All of lore.kernel.org
 help / color / mirror / Atom feed
From: Samuel Cabrero <scabrero@suse.com>
To: linux-cifs@vger.kernel.org
Cc: smfrench@gmail.com, pc@manguebit.com, ronniesahlberg@gmail.com,
	sprasad@microsoft.com, tom@talpey.com, bharathsm@microsoft.com,
	henrique.carvalho@suse.com, ematsumiya@suse.de,
	Samuel Cabrero <scabrero@suse.com>
Subject: [PATCH 15/18] smb:client: Do not store the tcon reference, find a matching one
Date: Fri, 10 Jul 2026 11:16:34 +0200	[thread overview]
Message-ID: <20260710091637.58873-16-scabrero@suse.com> (raw)
In-Reply-To: <20260710091637.58873-1-scabrero@suse.com>

Finally remove the cached tcon pointer from the registration.

Signed-off-by: Samuel Cabrero <scabrero@suse.com>
---
 fs/smb/client/cifs_swn.c | 227 ++++++++++++++++++++++++++++-----------
 fs/smb/client/trace.h    |   2 +
 2 files changed, 166 insertions(+), 63 deletions(-)

diff --git a/fs/smb/client/cifs_swn.c b/fs/smb/client/cifs_swn.c
index 6d8e3b94a432..97e415e441e4 100644
--- a/fs/smb/client/cifs_swn.c
+++ b/fs/smb/client/cifs_swn.c
@@ -42,8 +42,6 @@ struct cifs_swn_reg {
 		} ntlm;
 	} authinfo;
 
-	struct cifs_tcon *tcon;
-
 	unsigned long check_interval;
 	struct delayed_work check;
 };
@@ -288,13 +286,46 @@ static bool cifs_swn_reg_tcon_matches(const struct cifs_swn_reg *swnreg,
 	return true;
 }
 
+static struct cifs_tcon *
+cifs_swn_reg_get_tcon(const struct cifs_swn_reg *swnreg)
+{
+	struct TCP_Server_Info *server;
+	struct cifs_ses *ses;
+	struct cifs_tcon *tcon;
+
+	spin_lock(&cifs_tcp_ses_lock);
+	list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) {
+		if (SERVER_IS_CHAN(server))
+			continue;
+
+		list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
+			list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
+				spin_lock(&tcon->tc_lock);
+				if (tcon->status == TID_EXITING ||
+				    !cifs_swn_reg_tcon_matches(swnreg, tcon)) {
+					spin_unlock(&tcon->tc_lock);
+					continue;
+				}
+				++tcon->tc_count;
+				trace_smb3_tcon_ref(
+					tcon->debug_id, tcon->tc_count,
+					netfs_trace_tcon_ref_get_swn);
+				spin_unlock(&tcon->tc_lock);
+				spin_unlock(&cifs_tcp_ses_lock);
+				return tcon;
+			}
+		}
+	}
+	spin_unlock(&cifs_tcp_ses_lock);
+	return ERR_PTR(-ENOENT);
+}
+
 /*
  * Sends a register message to the userspace daemon based on the registration.
  * The authentication information to connect to the witness service is bundled
  * into the message.
  */
-static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg,
-					  struct cifs_tcon *tcon)
+static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg)
 {
 	struct sk_buff *skb;
 	struct genlmsghdr *hdr;
@@ -489,6 +520,7 @@ static void cifs_swn_reg_check(struct work_struct *work)
 {
 	struct cifs_swn_reg *swnreg =
 		container_of(work, struct cifs_swn_reg, check.work);
+	struct cifs_tcon *tcon;
 	int ret;
 
 	/*
@@ -507,12 +539,48 @@ static void cifs_swn_reg_check(struct work_struct *work)
 	}
 	mutex_unlock(&cifs_swnreg_idr_mutex);
 
+	tcon = cifs_swn_reg_get_tcon(swnreg);
+	if (IS_ERR(tcon)) {
+		ret = PTR_ERR(tcon);
+		cifs_dbg(FYI, "No matching tcon for registration id %d: %d\n",
+			 swnreg->id, ret);
+
+		/*
+		 * There is no point in keeping a live registration if there
+		 * are no matching tcons. Drop the reference that this fn
+		 * took at the top.
+		 */
+		mutex_lock(&cifs_swnreg_idr_mutex);
+		if (kref_put(&swnreg->ref_count, cifs_swn_reg_idr_remove)) {
+			mutex_unlock(&cifs_swnreg_idr_mutex);
+			cifs_swn_reg_release(swnreg);
+			return;
+		}
+
+		/*
+		 * Again, to drop the birth reference created via kref_init().
+		 * If there are other live references, next check run might release it.
+		 */
+		if (kref_put(&swnreg->ref_count, cifs_swn_reg_idr_remove)) {
+			mutex_unlock(&cifs_swnreg_idr_mutex);
+			cifs_swn_reg_release(swnreg);
+			return;
+		}
+		mutex_unlock(&cifs_swnreg_idr_mutex);
+
+		/* References remain: retry later. */
+		queue_delayed_work(cifsiod_wq, &swnreg->check,
+				   swnreg->check_interval);
+		return;
+	}
+	cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_swn);
+
 	/*
 	 * It is safe to send the registration message multiple times.
 	 * The userspace client library tracks if registered or not
 	 * using the swnreg->id.
 	 */
-	ret = cifs_swn_send_register_message(swnreg, swnreg->tcon);
+	ret = cifs_swn_send_register_message(swnreg);
 	if (ret < 0)
 		cifs_dbg(FYI, "%s: Failed to send register message: %d\n",
 			 __func__, ret);
@@ -618,8 +686,6 @@ static struct cifs_swn_reg *cifs_get_swn_reg(struct cifs_tcon *tcon)
 		(tcon->capabilities & SMB2_SHARE_CAP_ASYMMETRIC);
 	swnreg->ip_notify = false;
 
-	swnreg->tcon = tcon;
-
 	swnreg->check_interval = tcon->ses->server->echo_interval;
 	INIT_DELAYED_WORK(&swnreg->check, cifs_swn_reg_check);
 
@@ -641,8 +707,8 @@ static struct cifs_swn_reg *cifs_get_swn_reg(struct cifs_tcon *tcon)
 	return ERR_PTR(ret);
 }
 
-static int cifs_swn_resource_state_changed(struct cifs_tcon *tcon,
-					   const char *name, int state)
+static void cifs_swn_resource_state_changed(struct cifs_tcon *tcon,
+					    const char *name, int state)
 {
 	switch (state) {
 	case CIFS_SWN_RESOURCE_STATE_UNAVAILABLE:
@@ -657,7 +723,6 @@ static int cifs_swn_resource_state_changed(struct cifs_tcon *tcon,
 		cifs_dbg(FYI, "%s: resource name '%s' changed to unknown state\n", __func__, name);
 		break;
 	}
-	return 0;
 }
 
 static int cifs_swn_store_swn_addr(const struct sockaddr_storage *new,
@@ -691,31 +756,22 @@ static int cifs_swn_store_swn_addr(const struct sockaddr_storage *new,
 	return 0;
 }
 
-static int cifs_swn_reconnect(struct cifs_tcon *tcon, struct sockaddr_storage *addr)
+static bool cifs_swn_client_move(struct cifs_tcon *tcon,
+				 struct sockaddr_storage *addr)
 {
-	int ret = 0;
+	struct sockaddr_in *ipv4 = (struct sockaddr_in *)addr;
+	struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)addr;
+	int ret;
 
-	cifs_server_lock(tcon->ses->server);
+	if (addr->ss_family == AF_INET)
+		cifs_dbg(FYI, "%s: move to %pI4\n", __func__, &ipv4->sin_addr);
+	else if (addr->ss_family == AF_INET6)
+		cifs_dbg(FYI, "%s: move to %pI6\n", __func__, &ipv6->sin6_addr);
 
 	if (cifs_match_ipaddr((struct sockaddr *)&tcon->ses->server->dstaddr,
 			      (struct sockaddr *)addr)) {
 		/* no-op */
-		goto unlock;
-	}
-
-	/*
-	 * Unregister to stop receiving notifications for the old IP address.
-	 */
-	ret = cifs_swn_unregister(tcon);
-	if (ret < 0) {
-		cifs_dbg(VFS, "%s: Failed to unregister for witness notifications: %d\n",
-			 __func__, ret);
-		/*
-		 * Do not jump return on error, continue storing and registering for
-		 * notifications for the new address. There will be a stale registration
-		 * around running its periodic check task, which should cancel itself
-		 * if no matching any tcon.
-		 */
+		return false;
 	}
 
 	/* Store the reconnect address */
@@ -724,58 +780,103 @@ static int cifs_swn_reconnect(struct cifs_tcon *tcon, struct sockaddr_storage *a
 	if (ret < 0) {
 		cifs_dbg(VFS, "%s: failed to store address: %d\n", __func__,
 			 ret);
-		goto unlock;
+		return false;
 	}
 	tcon->ses->server->use_swn_dstaddr = true;
 
-	/*
-	 * And register to receive notifications for the new IP address now that we have
-	 * stored the new address.
-	 */
-	ret = cifs_swn_register(tcon);
-	if (ret < 0) {
-		cifs_dbg(VFS, "%s: Failed to register for witness notifications: %d\n",
-			 __func__, ret);
-		goto unlock;
-	}
-
 	cifs_signal_cifsd_for_reconnect(tcon->ses->server, false);
 
-unlock:
-	cifs_server_unlock(tcon->ses->server);
-
-	return ret;
+	return true;
 }
 
-static int cifs_swn_client_move(struct cifs_tcon *tcon,
-				struct sockaddr_storage *addr)
+/*
+ * This function applies the notification to a matching tcon.
+ * It is called holding a spinlock. Just sets the reconnect flag and
+ * store the reconnect address if necessary.
+ */
+static bool
+cifs_swn_handle_notification_tcon(const struct cifs_swn_notification *not,
+				  struct cifs_tcon *tcon)
 {
-	struct sockaddr_in *ipv4 = (struct sockaddr_in *)addr;
-	struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)addr;
-
-	if (addr->ss_family == AF_INET)
-		cifs_dbg(FYI, "%s: move to %pI4\n", __func__, &ipv4->sin_addr);
-	else if (addr->ss_family == AF_INET6)
-		cifs_dbg(FYI, "%s: move to %pI6\n", __func__, &ipv6->sin6_addr);
-
-	return cifs_swn_reconnect(tcon, addr);
+	switch (not->type) {
+	case CIFS_SWN_NOTIFICATION_RESOURCE_CHANGE:
+		cifs_swn_resource_state_changed(
+			tcon, not->data.resource_name_changed.name,
+			not->data.resource_name_changed.state);
+		return false;
+	case CIFS_SWN_NOTIFICATION_CLIENT_MOVE:
+		return cifs_swn_client_move(tcon, not->data.client_move.addr);
+	default:
+		cifs_dbg(FYI, "%s: unknown notification type %d\n", __func__,
+			 not->type);
+		break;
+	}
+	return false;
 }
 
+/*
+ * This function process a notification received for a registration. It
+ * searches all matching tcons to deliver it and then unregisters/registers
+ * as necessary if the address has changed.
+ */
 static int cifs_swn_handle_notification(const struct cifs_swn_notification *not)
 {
+	struct TCP_Server_Info *server;
+	struct cifs_ses *ses;
+	struct cifs_tcon *tcon;
+	int ret;
+
+	/* Walk the tcons and apply the notification to matching ones */
+	spin_lock(&cifs_tcp_ses_lock);
+	list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) {
+		if (SERVER_IS_CHAN(server))
+			continue;
+
+		cifs_server_lock(server);
+		list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
+			list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
+				spin_lock(&tcon->tc_lock);
+				if (tcon->status == TID_EXITING ||
+				    !cifs_swn_reg_tcon_matches(not->swnreg,
+							       tcon)) {
+					spin_unlock(&tcon->tc_lock);
+					continue;
+				}
+				cifs_swn_handle_notification_tcon(not, tcon);
+				spin_unlock(&tcon->tc_lock);
+			}
+		}
+		cifs_server_unlock(server);
+	}
+	spin_unlock(&cifs_tcp_ses_lock);
+
 	switch (not->type) {
-	case CIFS_SWN_NOTIFICATION_RESOURCE_CHANGE:
-		return cifs_swn_resource_state_changed(
-			not->swnreg->tcon, not->data.resource_name_changed.name,
-			not->data.resource_name_changed.state);
 	case CIFS_SWN_NOTIFICATION_CLIENT_MOVE:
-		return cifs_swn_client_move(not->swnreg->tcon,
-					    not->data.client_move.addr);
+		/* Unregister from the current address */
+		ret = cifs_swn_send_unregister_message(not->swnreg);
+		if (ret < 0) {
+			cifs_dbg(VFS,
+				 "%s: Failed to unregister for witness notifications: %d\n",
+				 __func__, ret);
+		}
+
+		/* Store the new address */
+		not->swnreg->addr = *not->data.client_move.addr;
+
+		/* Register for this new address */
+		ret = cifs_swn_send_register_message(not->swnreg);
+		if (ret < 0) {
+			cifs_dbg(VFS,
+				 "%s: Failed to register for witness notifications: %d\n",
+				 __func__, ret);
+		}
+		break;
 	default:
 		cifs_dbg(FYI, "%s: unknown notification type %d\n", __func__,
 			 not->type);
 		break;
 	}
+
 	return 0;
 }
 
@@ -889,7 +990,7 @@ int cifs_swn_register(struct cifs_tcon *tcon)
 	if (IS_ERR(swnreg))
 		return PTR_ERR(swnreg);
 
-	ret = cifs_swn_send_register_message(swnreg, tcon);
+	ret = cifs_swn_send_register_message(swnreg);
 	if (ret < 0) {
 		cifs_dbg(VFS, "%s: Failed to send swn register message: %d\n", __func__, ret);
 		/* Do not put the swnreg or return error, the check task will retry */
diff --git a/fs/smb/client/trace.h b/fs/smb/client/trace.h
index b99ec5a417fa..2b4cbffd2931 100644
--- a/fs/smb/client/trace.h
+++ b/fs/smb/client/trace.h
@@ -181,6 +181,7 @@
 	EM(netfs_trace_tcon_ref_get_find,		"GET Find  ") \
 	EM(netfs_trace_tcon_ref_get_find_sess_tcon,	"GET FndSes") \
 	EM(netfs_trace_tcon_ref_get_reconnect_server,	"GET Reconn") \
+	EM(netfs_trace_tcon_ref_get_swn,		"GET Swn   ") \
 	EM(netfs_trace_tcon_ref_new,			"NEW       ") \
 	EM(netfs_trace_tcon_ref_new_ipc,		"NEW Ipc   ") \
 	EM(netfs_trace_tcon_ref_new_reconnect_server,	"NEW Reconn") \
@@ -193,6 +194,7 @@
 	EM(netfs_trace_tcon_ref_put_dfs_refer,		"PUT DfsRfr") \
 	EM(netfs_trace_tcon_ref_put_reconnect_server,	"PUT Reconn") \
 	EM(netfs_trace_tcon_ref_put_tlink,		"PUT Tlink ") \
+	EM(netfs_trace_tcon_ref_put_swn,		"PUT Swn   ") \
 	EM(netfs_trace_tcon_ref_see_cancelled_close,	"SEE Cn-Cls") \
 	EM(netfs_trace_tcon_ref_see_fscache_collision,	"SEE FV-CO!") \
 	EM(netfs_trace_tcon_ref_see_fscache_okay,	"SEE FV-Ok ") \
-- 
2.54.0


  parent reply	other threads:[~2026-07-10  9:17 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-10  9:16 [PATCH 00/18] SWN: Decouple witness registrations and tcons Samuel Cabrero
2026-07-10  9:16 ` [PATCH 01/18] Revert "smb: client: resolve SWN tcon from live registrations" Samuel Cabrero
2026-07-10  9:16 ` [PATCH 02/18] smb:client: Adjust witness notification request Samuel Cabrero
2026-07-10  9:16 ` [PATCH 03/18] smb:client: Rename variable to match the rest of the code Samuel Cabrero
2026-07-10  9:16 ` [PATCH 04/18] smb:client: Split idr remove and release functions Samuel Cabrero
2026-07-10  9:16 ` [PATCH 05/18] smb:client: Add a specific task to refresh witness registrations Samuel Cabrero
2026-07-10  9:16 ` [PATCH 06/18] smb:client: Store the tcon dstaddr in the witness registration Samuel Cabrero
2026-07-10  9:16 ` [PATCH 07/18] smb:client: Fix printf format string Samuel Cabrero
2026-07-10  9:16 ` [PATCH 08/18] smb:client: Save memory allocations Samuel Cabrero
2026-07-10  9:16 ` [PATCH 09/18] smb:client: Use the correct return code Samuel Cabrero
2026-07-10  9:16 ` [PATCH 10/18] smb:client: Improve tcon matching logic Samuel Cabrero
2026-07-10  9:16 ` [PATCH 11/18] smb:client: Pass the tcon to cifs_swn_send_register_message Samuel Cabrero
2026-07-10  9:16 ` [PATCH 12/18] smb:client: Pass the tcon to notification handlers Samuel Cabrero
2026-07-10  9:16 ` [PATCH 13/18] smb:client: Introduce swn_notification struct Samuel Cabrero
2026-07-10  9:16 ` [PATCH 14/18] smb:client: Cache auth info Samuel Cabrero
2026-07-10  9:16 ` Samuel Cabrero [this message]
2026-07-10  9:16 ` [PATCH 16/18] smb:client: Simplify registration creation Samuel Cabrero
2026-07-10  9:16 ` [PATCH 17/18] smb:client: Improve registration network and share names handling Samuel Cabrero
2026-07-10  9:16 ` [PATCH 18/18] smb:client: Avoid witness re-register if the address did not change Samuel Cabrero
2026-07-10 14:16 ` [PATCH 00/18] SWN: Decouple witness registrations and tcons Enzo Matsumiya

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260710091637.58873-16-scabrero@suse.com \
    --to=scabrero@suse.com \
    --cc=bharathsm@microsoft.com \
    --cc=ematsumiya@suse.de \
    --cc=henrique.carvalho@suse.com \
    --cc=linux-cifs@vger.kernel.org \
    --cc=pc@manguebit.com \
    --cc=ronniesahlberg@gmail.com \
    --cc=smfrench@gmail.com \
    --cc=sprasad@microsoft.com \
    --cc=tom@talpey.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.