Blob Blame History Raw
From: Paulo Alcantara <pc@cjr.nz>
Date: Tue, 13 Dec 2022 01:23:16 -0300
Subject: [PATCH] cifs: fix refresh of cached referrals
Git-commit: 6916881f443f67f6893b504fa2171468c8aed915
References: bsc#1193629
Patch-mainline: v6.2-rc1

We can't rely on cifs_tcon::ses to refresh cached referral as the
server target might not respond to referrals, e.g. share is not hosted
in a DFS root server.  Consider the following

  mount //dom/dfs/link -> /root1/dfs/link -> /fs0/share

where fs0 can't get a referral for "/root1/dfs/link".

To simplify and fix the access of dfs root sessions, store the dfs
root session pointer directly to new sessions so making it easier to
select the appropriate ipc connection and use it for failover or cache
refresh.

Signed-off-by: Paulo Alcantara (SUSE) <pc@cjr.nz>
Signed-off-by: Steve French <stfrench@microsoft.com>
Acked-by: Paulo Alcantara <palcantara@suse.de>
---
 fs/cifs/cifsglob.h  |   3 +
 fs/cifs/connect.c   |   5 +-
 fs/cifs/dfs.c       |   6 ++
 fs/cifs/dfs_cache.c | 140 ++++++++------------------------------------
 4 files changed, 37 insertions(+), 117 deletions(-)

diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
index 2e2976f1874f..cfdd5bf701a1 100644
--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -107,6 +107,8 @@
 
 #define CIFS_MAX_WORKSTATION_LEN  (__NEW_UTS_LEN + 1)  /* reasonable max for client */
 
+#define CIFS_DFS_ROOT_SES(ses) ((ses)->dfs_root_ses ?: (ses))
+
 /*
  * CIFS vfs client Status information (based on what we know.)
  */
@@ -1099,6 +1101,7 @@ struct cifs_ses {
 	 */
 	unsigned long chans_need_reconnect;
 	/* ========= end: protected by chan_lock ======== */
+	struct cifs_ses *dfs_root_ses;
 };
 
 static inline bool
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
index a66cb23a954e..db3a2b3ac497 100644
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -4150,7 +4150,8 @@ static int __tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *t
 	int rc;
 	struct TCP_Server_Info *server = tcon->ses->server;
 	const struct smb_version_operations *ops = server->ops;
-	struct cifs_tcon *ipc = tcon->ses->tcon_ipc;
+	struct cifs_ses *root_ses = CIFS_DFS_ROOT_SES(tcon->ses);
+	struct cifs_tcon *ipc = root_ses->tcon_ipc;
 	char *share = NULL, *prefix = NULL;
 	const char *tcp_host;
 	size_t tcp_host_len;
@@ -4208,7 +4209,7 @@ static int __tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *t
 		 * reconnect so either the demultiplex thread or the echo worker will reconnect to
 		 * newly resolved target.
 		 */
-		if (dfs_cache_find(xid, tcon->ses, cifs_sb->local_nls, cifs_remap(cifs_sb), target,
+		if (dfs_cache_find(xid, root_ses, cifs_sb->local_nls, cifs_remap(cifs_sb), target,
 				   NULL, &ntl)) {
 			rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls);
 			if (rc)
diff --git a/fs/cifs/dfs.c b/fs/cifs/dfs.c
index 88a0cab335b4..fbc8e880a1fe 100644
--- a/fs/cifs/dfs.c
+++ b/fs/cifs/dfs.c
@@ -95,7 +95,13 @@ static int get_session(struct cifs_mount_ctx *mnt_ctx, const char *full_path)
 	ctx->leaf_fullpath = (char *)full_path;
 	rc = cifs_mount_get_session(mnt_ctx);
 	ctx->leaf_fullpath = NULL;
+	if (!rc) {
+		struct cifs_ses *ses = mnt_ctx->ses;
 
+		mutex_lock(&ses->session_mutex);
+		ses->dfs_root_ses = mnt_ctx->root_ses;
+		mutex_unlock(&ses->session_mutex);
+	}
 	return rc;
 }
 
diff --git a/fs/cifs/dfs_cache.c b/fs/cifs/dfs_cache.c
index bef672534397..95cc3951420e 100644
--- a/fs/cifs/dfs_cache.c
+++ b/fs/cifs/dfs_cache.c
@@ -83,27 +83,6 @@ 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->tree_name))
-			return *ses;
-	}
-	return ERR_PTR(-ENOENT);
-}
-
 static void __mount_group_release(struct mount_group *mg)
 {
 	int i;
@@ -760,8 +739,6 @@ static int get_dfs_referral(const unsigned int xid, struct cifs_ses *ses, const
 	int rc;
 	int i;
 
-	cifs_dbg(FYI, "%s: get an DFS referral for %s\n", __func__, path);
-
 	*refs = NULL;
 	*numrefs = 0;
 
@@ -770,6 +747,7 @@ static int get_dfs_referral(const unsigned int xid, struct cifs_ses *ses, const
 	if (unlikely(!cache_cp))
 		return -EINVAL;
 
+	cifs_dbg(FYI, "%s: ipc=%s referral=%s\n", __func__, ses->tcon_ipc->tree_name, path);
 	rc =  ses->server->ops->get_dfs_refer(xid, ses, path, refs, numrefs, cache_cp,
 					      NO_MAP_UNI_RSVD);
 	if (!rc) {
@@ -1366,10 +1344,9 @@ static void mark_for_reconnect_if_needed(struct cifs_tcon *tcon, struct dfs_cach
 }
 
 /* Refresh dfs referral of tcon and mark it for reconnect if needed */
-static int __refresh_tcon(const char *path, struct cifs_ses **sessions, struct cifs_tcon *tcon,
-			  bool force_refresh)
+static int __refresh_tcon(const char *path, struct cifs_tcon *tcon, bool force_refresh)
 {
-	struct cifs_ses *ses;
+	struct cifs_ses *ses = CIFS_DFS_ROOT_SES(tcon->ses);
 	struct cache_entry *ce;
 	struct dfs_info3_param *refs = NULL;
 	int numrefs = 0;
@@ -1378,11 +1355,7 @@ static int __refresh_tcon(const char *path, struct cifs_ses **sessions, struct c
 	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);
-	}
+	xid = get_xid();
 
 	down_read(&htable_rw_lock);
 	ce = lookup_cache_entry(path);
@@ -1399,12 +1372,9 @@ static int __refresh_tcon(const char *path, struct cifs_ses **sessions, struct c
 		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) {
+		/* Create or update a cache entry with the new referral */
 		dump_refs(refs, numrefs);
 
 		down_write(&htable_rw_lock);
@@ -1419,24 +1389,20 @@ static int __refresh_tcon(const char *path, struct cifs_ses **sessions, struct c
 	}
 
 out:
+	free_xid(xid);
 	dfs_cache_free_tgts(&tl);
 	free_dfs_info_array(refs, numrefs);
 	return rc;
 }
 
-static int refresh_tcon(struct cifs_ses **sessions, struct cifs_tcon *tcon, bool force_refresh)
+static int refresh_tcon(struct cifs_tcon *tcon, bool force_refresh)
 {
 	struct TCP_Server_Info *server = tcon->ses->server;
 
 	mutex_lock(&server->refpath_lock);
-	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);
-	}
+	if (server->leaf_fullpath)
+		__refresh_tcon(server->leaf_fullpath + 1, tcon, force_refresh);
 	mutex_unlock(&server->refpath_lock);
-
 	return 0;
 }
 
@@ -1454,9 +1420,6 @@ int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
 {
 	struct cifs_tcon *tcon;
 	struct TCP_Server_Info *server;
-	struct mount_group *mg;
-	struct cifs_ses *sessions[CACHE_MAX_ENTRIES + 1] = {NULL};
-	int rc;
 
 	if (!cifs_sb || !cifs_sb->master_tlink)
 		return -EINVAL;
@@ -1473,21 +1436,6 @@ int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
 		cifs_dbg(FYI, "%s: no dfs mount group id\n", __func__);
 		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);
-		cifs_dbg(FYI, "%s: no ipc session for refreshing referral\n", __func__);
-		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).
@@ -1498,17 +1446,15 @@ int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
 	 * 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;
+	return refresh_tcon(tcon, true);
 }
 
 /*
- * Refresh all active dfs mounts regardless of whether they are in cache or not.
- * (cache can be cleared)
+ * Worker that will refresh DFS cache from all active mounts based on lowest TTL value
+ * from a DFS referral.
  */
-static void refresh_mounts(struct cifs_ses **sessions)
+static void refresh_cache_worker(struct work_struct *work)
 {
 	struct TCP_Server_Info *server;
 	struct cifs_ses *ses;
@@ -1523,9 +1469,19 @@ static void refresh_mounts(struct cifs_ses **sessions)
 			continue;
 
 		list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
+			struct cifs_ses *root_ses = CIFS_DFS_ROOT_SES(ses);
+
+			spin_lock(&root_ses->ses_lock);
+			if (root_ses->ses_status != SES_GOOD) {
+				spin_unlock(&root_ses->ses_lock);
+				continue;
+			}
+			spin_unlock(&root_ses->ses_lock);
+
 			list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
 				spin_lock(&tcon->tc_lock);
-				if (!tcon->ipc && !tcon->need_reconnect) {
+				if (!tcon->ipc && tcon->status != TID_NEW &&
+				    tcon->status != TID_NEED_TCON) {
 					tcon->tc_count++;
 					list_add_tail(&tcon->ulist, &tcons);
 				}
@@ -1542,57 +1498,11 @@ static void refresh_mounts(struct cifs_ses **sessions)
 
 		mutex_lock(&server->refpath_lock);
 		if (server->leaf_fullpath)
-			__refresh_tcon(server->leaf_fullpath + 1, sessions, tcon, false);
+			__refresh_tcon(server->leaf_fullpath + 1, tcon, false);
 		mutex_unlock(&server->refpath_lock);
 
 		cifs_put_tcon(tcon);
 	}
-}
-
-/*
- * Worker that will refresh DFS cache and active mounts based on lowest TTL value from a DFS
- * referral.
- */
-static void refresh_cache_worker(struct work_struct *work)
-{
-	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);
-	}
-	mutex_unlock(&mount_group_list_lock);
-
-	/* 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;
-
-		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;
-	}
-
-	if (sessions[0])
-		refresh_mounts(sessions);
-
-	list_for_each_entry_safe(mg, tmp_mg, &mglist, refresh_list) {
-		list_del_init(&mg->refresh_list);
-		kref_put(&mg->refcount, mount_group_release);
-	}
 
 	spin_lock(&cache_ttl_lock);
 	queue_delayed_work(dfscache_wq, &refresh_task, cache_ttl * HZ);
-- 
2.39.0