FAILED: patch "[PATCH] smb: client: resolve SWN tcon from live registrations" failed to apply to 6.1-stable tree
Subsystems (4)
[email protected]
maintainerThe patch below does not apply to the 6.1-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.1.y
git checkout FETCH_HEAD
git cherry-pick -x ec457f9afe5ae9538bdcd58fd4cb442b9787e183
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026071415-visiting-rise-04bc@gregkh' --subject-prefix 'PATCH 6.1.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From ec457f9afe5ae9538bdcd58fd4cb442b9787e183 Mon Sep 17 00:00:00 2001
From: Michael Bommarito <[email protected]>
Date: Sun, 17 May 2026 20:11:49 -0400
Subject: [PATCH] smb: client: resolve SWN tcon from live registrations
cifs_swn_notify() looks up a witness registration by id under cifs_swnreg_idr_mutex, drops the mutex, and then uses the registration's cached tcon pointer. That pointer is not a lifetime reference, and it is not a stable representative once cifs_get_swn_reg() lets multiple tcons for the same net/share name share one registration id.
A same-share second mount can keep the cifs_swn_reg alive after the first tcon unregisters and is freed. The registration then still points at the freed first tcon, so taking tc_lock or incrementing tc_count through swnreg->tcon only moves the use-after-free earlier. Taking tc_lock while holding cifs_swnreg_idr_mutex also violates the documented CIFS lock order.
Fix this by making the registration store only the stable witness
identity: id, net name, share name, and notify flags. When a notify arrives, copy that identity under cifs_swnreg_idr_mutex, drop the mutex, then find and pin a live witness tcon that currently matches the net/share pair under the normal cifs_tcp_ses_lock -> tc_lock order. The notification path uses that pinned tcon directly and drops the reference when done.
Registration and unregister messages now use the live tcon passed by the caller instead of a cached tcon in the registration. The final unregister send is folded into cifs_swn_unregister() while the registration is still protected by cifs_swnreg_idr_mutex. This removes the previous find/drop/reacquire raw-pointer window. The release path only removes the idr entry and frees the stable identity strings.
This preserves the intended one-registration/many-tcon behavior: a registration id represents a net/share pair, and notify handling acts on a live representative selected at use time. It also preserves CLIENT_MOVE ordering for the representative tcon because the old-IP unregister is sent before cifs_swn_register() sends the new-IP register.
fs/smb/client/cifs_swn.c +261 -55
@@ -28,10 +28,54 @@ struct cifs_swn_reg {bool net_name_notify;bool share_name_notify;bool ip_notify; - -struct cifs_tcon *tcon; }; +struct cifs_swn_reg_info { +int id; +unsigned int ref_count; +const char *net_name; +const char *share_name; +bool net_name_notify; +bool share_name_notify; +bool ip_notify; +}; + +static void cifs_swn_snapshot_reg (struct cifs_swn_reg *swnreg, +struct cifs_swn_reg_info *info) +{ + info->id = swnreg->id; + info->ref_count =kref_read (&swnreg->ref_count); + info->net_name = swnreg->net_name; + info->share_name = swnreg->share_name; + info->net_name_notify = swnreg->net_name_notify; + info->share_name_notify = swnreg->share_name_notify; + info->ip_notify = swnreg->ip_notify; +} + +static int cifs_swn_dup_reg (struct cifs_swn_reg *swnreg, +struct cifs_swn_reg_info *info) +{ +cifs_swn_snapshot_reg (swnreg, info); + + info->net_name =kstrdup (swnreg->net_name,GFP_KERNEL ); +if (!info->net_name) +return -ENOMEM ; + + info->share_name =kstrdup (swnreg->share_name,GFP_KERNEL ); +if (!info->share_name) { +kfree (info->net_name); +return -ENOMEM ; + } + +return 0 ; +} + +static void cifs_swn_free_reg_info (struct cifs_swn_reg_info *info) +{ +kfree (info->net_name); +kfree (info->share_name); +} +static int cifs_swn_auth_info_krb (struct cifs_tcon *tcon,struct sk_buff *skb) {int ret;
@@ -73,7 +117,8 @@ static int cifs_swn_auth_info_ntlm(struct cifs_tcon *tcon, struct sk_buff *skb)* 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) +static int cifs_swn_send_register_message (struct cifs_swn_reg_info *swnreg, +struct cifs_tcon *tcon) {struct sk_buff *skb;struct genlmsghdr *hdr;
@@ -109,10 +154,10 @@ static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg)* told to switch to it (client move message). In these cases we unregister from the * server address and register to the new address when we receive the notification. */ -if (swnreg->tcon->ses->server->use_swn_dstaddr) - addr = &swnreg->tcon->ses->server->swn_dstaddr; +if (tcon->ses->server->use_swn_dstaddr) + addr = &tcon->ses->server->swn_dstaddr;else - addr = &swnreg->tcon->ses->server->dstaddr; + addr = &tcon->ses->server->dstaddr; ret =nla_put (skb,CIFS_GENL_ATTR_SWN_IP ,sizeof (struct sockaddr_storage ), addr);if (ret <0 )
@@ -136,10 +181,10 @@ static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg) goto nlmsg_fail; } - authtype =cifs_select_sectype (swnreg->tcon->ses->server, swnreg->tcon->ses->sectype); + authtype =cifs_select_sectype (tcon->ses->server, tcon->ses->sectype);switch (authtype) {case Kerberos: - ret =cifs_swn_auth_info_krb (swnreg->tcon, skb); + ret =cifs_swn_auth_info_krb (tcon, skb);if (ret <0 ) {cifs_dbg (VFS ,"%s: Failed to get kerberos auth info: %d\n" , __func__, ret); goto nlmsg_fail;
@@ -147,7 +192,7 @@ static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg)break ;case NTLMv2:case RawNTLMSSP: - ret =cifs_swn_auth_info_ntlm (swnreg->tcon, skb); + ret =cifs_swn_auth_info_ntlm (tcon, skb);if (ret <0 ) {cifs_dbg (VFS ,"%s: Failed to get NTLM auth info: %d\n" , __func__, ret); goto nlmsg_fail;
@@ -176,7 +221,8 @@ static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg)/* * Sends an uregister message to the userspace daemon based on the registration */ -static int cifs_swn_send_unregister_message (struct cifs_swn_reg *swnreg) +static int cifs_swn_send_unregister_message (struct cifs_swn_reg_info *swnreg, +struct cifs_tcon *tcon) {struct sk_buff *skb;struct genlmsghdr *hdr;
@@ -205,7 +251,7 @@ static int cifs_swn_send_unregister_message(struct cifs_swn_reg *swnreg) goto nlmsg_fail; ret =nla_put (skb,CIFS_GENL_ATTR_SWN_IP ,sizeof (struct sockaddr_storage ), - &swnreg->tcon->ses->server->dstaddr); + &tcon->ses->server->dstaddr);if (ret <0 ) goto nlmsg_fail;
@@ -241,6 +287,88 @@ static int cifs_swn_send_unregister_message(struct cifs_swn_reg *swnreg)return ret; } +/* +* Allocation-free mirror of extract_hostname() + extract_sharename() from +* fs/smb/client/unc.c. Those helpers kmalloc(GFP_KERNEL); this runs under +* cifs_tcp_ses_lock and tcon->tc_lock, both spinlocks, so we mirror their +* parsing in place against the caller's stable net_name/share_name strings. +* Keep in sync with unc.c. +*/ +static bool cifs_swn_tcon_matches (struct cifs_tcon *tcon, +const char *net_name, +const char *share_name) +{ +const char *unc = tcon->tree_name; +const char *host, *share, *delim; +size_t host_len, share_len; + +if (!tcon->use_witness) +return false; + +/* extract_hostname: require strlen(unc) >= 3 */ +if (strnlen (unc,3 ) <3 ) +return false; +/* extract_hostname: skip all leading '\' characters */ +for (host = unc; *host =='\\' ; host++) + ; +if (!*host) +return false; + delim =strchr (host,'\\' ); +if (!delim) +return false; + host_len = delim - host; +if (strlen (net_name) != host_len || +strncasecmp (host, net_name, host_len)) +return false; + +/* extract_sharename: start at unc + 2, then first '\' onward */ + share = unc +2 ; + delim =strchr (share,'\\' ); +if (!delim) +return false; + share = delim +1 ; + share_len =strlen (share); + +return strlen (share_name) == share_len && + !strncasecmp (share, share_name, share_len); +} + +/* +* One SWN registration id represents one net/share name pair. Multiple +* mounted tcons can therefore share the id. Pick a live representative at +* use time instead of caching the first tcon pointer in the registration. +*/ +static struct cifs_tcon *cifs_swn_get_tcon (struct cifs_swn_reg_info *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) { +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_tcon_matches (tcon, swnreg->net_name, + swnreg->share_name)) { +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_notify); +spin_unlock (&tcon->tc_lock); +spin_unlock (&cifs_tcp_ses_lock); +return tcon; + } + } + } +spin_unlock (&cifs_tcp_ses_lock); +return NULL ; +} +/* * Try to find a matching registration for the tcon's server name and share name. * Calls to this function must be protected by cifs_swnreg_idr_mutex.
@@ -347,8 +475,6 @@ static struct cifs_swn_reg *cifs_get_swn_reg(struct cifs_tcon *tcon) reg->net_name_notify = true; reg->share_name_notify = true; reg->ip_notify = (tcon->capabilities &SMB2_SHARE_CAP_SCALEOUT ); - - reg->tcon = tcon; unlock:mutex_unlock (&cifs_swnreg_idr_mutex);
@@ -368,11 +494,6 @@ static struct cifs_swn_reg *cifs_get_swn_reg(struct cifs_tcon *tcon)static void cifs_swn_reg_release (struct kref *ref) { struct cifs_swn_reg *swnreg = container_of(ref, struct cifs_swn_reg, ref_count); -int ret; - - ret =cifs_swn_send_unregister_message (swnreg); -if (ret <0 ) -cifs_dbg (VFS ,"%s: Failed to send unregister message: %d\n" , __func__, ret);idr_remove (&cifs_swnreg_idr, swnreg->id);kfree (swnreg->net_name);
@@ -380,23 +501,33 @@ static void cifs_swn_reg_release(struct kref *ref)kfree (swnreg); } -static void cifs_put_swn_reg (struct cifs_swn_reg *swnreg) +static void cifs_put_swn_reg_locked (struct cifs_swn_reg *swnreg, +struct cifs_tcon *tcon) { -mutex_lock (&cifs_swnreg_idr_mutex); +if (kref_read (&swnreg->ref_count) ==1 ) { +struct cifs_swn_reg_info swnreg_info; +int ret; + +cifs_swn_snapshot_reg (swnreg, &swnreg_info); + ret =cifs_swn_send_unregister_message (&swnreg_info, tcon); +if (ret <0 ) +cifs_dbg (VFS ,"%s: Failed to send unregister message: %d\n" , + __func__, ret); + } +kref_put (&swnreg->ref_count, cifs_swn_reg_release); -mutex_unlock (&cifs_swnreg_idr_mutex); } -static int cifs_swn_resource_state_changed (struct cifs_swn_reg *swnreg,const char *name,int state) +static int cifs_swn_resource_state_changed (struct cifs_tcon *tcon,const char *name,int state) {switch (state) {case CIFS_SWN_RESOURCE_STATE_UNAVAILABLE :cifs_dbg (FYI ,"%s: resource name '%s' become unavailable\n" , __func__, name); -cifs_signal_cifsd_for_reconnect (swnreg->tcon->ses->server, true); +cifs_signal_cifsd_for_reconnect (tcon->ses->server, true);break ;case CIFS_SWN_RESOURCE_STATE_AVAILABLE :cifs_dbg (FYI ,"%s: resource name '%s' become available\n" , __func__, name); -cifs_signal_cifsd_for_reconnect (swnreg->tcon->ses->server, true); +cifs_signal_cifsd_for_reconnect (tcon->ses->server, true);break ;case CIFS_SWN_RESOURCE_STATE_UNKNOWN :cifs_dbg (FYI ,"%s: resource name '%s' changed to unknown state\n" , __func__, name);
@@ -502,7 +633,7 @@ static int cifs_swn_reconnect(struct cifs_tcon *tcon, struct sockaddr_storage *areturn ret; } -static int cifs_swn_client_move (struct cifs_swn_reg *swnreg,struct sockaddr_storage *addr) +static int cifs_swn_client_move (struct cifs_tcon *tcon,struct sockaddr_storage *addr) {struct sockaddr_in *ipv4 = (struct sockaddr_in *)addr;struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)addr;
@@ -512,14 +643,17 @@ static int cifs_swn_client_move(struct cifs_swn_reg *swnreg, struct sockaddr_stoelse if (addr->ss_family ==AF_INET6 )cifs_dbg (FYI ,"%s: move to %pI6\n" , __func__, &ipv6->sin6_addr); -return cifs_swn_reconnect (swnreg->tcon, addr); +return cifs_swn_reconnect (tcon, addr); }int cifs_swn_notify (struct sk_buff *skb,struct genl_info *info) {struct cifs_swn_reg *swnreg; +struct cifs_swn_reg_info swnreg_info; +struct cifs_tcon *tcon;char name[256 ];int type; +int ret =0 ;if (info->attrs[CIFS_GENL_ATTR_SWN_REGISTRATION_ID ]) {int swnreg_id;
@@ -527,21 +661,34 @@ int cifs_swn_notify(struct sk_buff *skb, struct genl_info *info) swnreg_id =nla_get_u32 (info->attrs[CIFS_GENL_ATTR_SWN_REGISTRATION_ID ]);mutex_lock (&cifs_swnreg_idr_mutex); swnreg =idr_find (&cifs_swnreg_idr, swnreg_id); -mutex_unlock (&cifs_swnreg_idr_mutex);if (swnreg ==NULL ) { +mutex_unlock (&cifs_swnreg_idr_mutex);cifs_dbg (FYI ,"%s: registration id %d not found\n" , __func__, swnreg_id);return -EINVAL ; } + ret =cifs_swn_dup_reg (swnreg, &swnreg_info); +mutex_unlock (&cifs_swnreg_idr_mutex); +if (ret) +return ret; }else {cifs_dbg (FYI ,"%s: missing registration id attribute\n" , __func__);return -EINVAL ; } + tcon =cifs_swn_get_tcon (&swnreg_info); +if (!tcon) { +cifs_dbg (FYI ,"%s: registration id %d has no live tcon\n" , + __func__, swnreg_info.id); + ret = -ENODEV ; + goto free_info; + } +if (info->attrs[CIFS_GENL_ATTR_SWN_NOTIFICATION_TYPE ]) { type =nla_get_u32 (info->attrs[CIFS_GENL_ATTR_SWN_NOTIFICATION_TYPE ]); }else {cifs_dbg (FYI ,"%s: missing notification type attribute\n" , __func__); -return -EINVAL ; + ret = -EINVAL ; + goto out; }switch (type) {
@@ -553,15 +700,18 @@ int cifs_swn_notify(struct sk_buff *skb, struct genl_info *info) sizeof(name)); }else {cifs_dbg (FYI ,"%s: missing resource name attribute\n" , __func__); -return -EINVAL ; + ret = -EINVAL ; + goto out; }if (info->attrs[CIFS_GENL_ATTR_SWN_RESOURCE_STATE ]) { state =nla_get_u32 (info->attrs[CIFS_GENL_ATTR_SWN_RESOURCE_STATE ]); }else {cifs_dbg (FYI ,"%s: missing resource state attribute\n" , __func__); -return -EINVAL ; + ret = -EINVAL ; + goto out; } -return cifs_swn_resource_state_changed (swnreg, name, state); + ret =cifs_swn_resource_state_changed (tcon, name, state); +break ; }case CIFS_SWN_NOTIFICATION_CLIENT_MOVE : {struct sockaddr_storage addr;
@@ -570,28 +720,36 @@ int cifs_swn_notify(struct sk_buff *skb, struct genl_info *info)nla_memcpy (&addr, info->attrs[CIFS_GENL_ATTR_SWN_IP ],sizeof (addr)); }else {cifs_dbg (FYI ,"%s: missing IP address attribute\n" , __func__); -return -EINVAL ; + ret = -EINVAL ; + goto out; } -return cifs_swn_client_move (swnreg, &addr); + ret =cifs_swn_client_move (tcon, &addr); +break ; }default :cifs_dbg (FYI ,"%s: unknown notification type %d\n" , __func__, type);break ; } -return 0 ; +out: +cifs_put_tcon (tcon, netfs_trace_tcon_ref_put_swn_notify); +free_info: +cifs_swn_free_reg_info (&swnreg_info); +return ret; }int cifs_swn_register (struct cifs_tcon *tcon) {struct cifs_swn_reg *swnreg; +struct cifs_swn_reg_info swnreg_info;int ret; swnreg =cifs_get_swn_reg (tcon);if (IS_ERR (swnreg))return PTR_ERR (swnreg); - ret =cifs_swn_send_register_message (swnreg); +cifs_swn_snapshot_reg (swnreg, &swnreg_info); + ret =cifs_swn_send_register_message (&swnreg_info, tcon);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 echo task will retry */
@@ -612,35 +770,68 @@ int cifs_swn_unregister(struct cifs_tcon *tcon)return PTR_ERR (swnreg); } +cifs_put_swn_reg_locked (swnreg, tcon);mutex_unlock (&cifs_swnreg_idr_mutex); -cifs_put_swn_reg (swnreg); -return 0 ; } -void cifs_swn_dump (struct seq_file *m) +/* +* Snapshot one registration under cifs_swnreg_idr_mutex and return. Callers +* intentionally do the per-registration network/genlmsg work without the +* mutex held, both to keep the critical section short and to avoid nesting +* cifs_swnreg_idr_mutex inside the higher tc_lock when a live tcon is then +* pinned for the send. +*/ +static int cifs_swn_get_next_reg_info (int *id,struct cifs_swn_reg_info *info) {struct cifs_swn_reg *swnreg; +int ret =0 ; + +mutex_lock (&cifs_swnreg_idr_mutex); + swnreg =idr_get_next (&cifs_swnreg_idr, id); +if (swnreg) { + ret =cifs_swn_dup_reg (swnreg, info); +if (!ret) { + *id = swnreg->id +1 ; + ret =1 ; + } + } +mutex_unlock (&cifs_swnreg_idr_mutex); + +return ret; +} + +void cifs_swn_dump (struct seq_file *m) +{ +struct cifs_swn_reg_info swnreg_info; +struct cifs_tcon *tcon;struct sockaddr_in *sa;struct sockaddr_in6 *sa6; -int id; +int id =0 ; +int ret;seq_puts (m,"Witness registrations:" ); -mutex_lock (&cifs_swnreg_idr_mutex); -idr_for_each_entry (&cifs_swnreg_idr, swnreg, id) { +while ((ret =cifs_swn_get_next_reg_info (&id, &swnreg_info)) >0 ) {seq_printf (m,"\nId: %u Refs: %u Network name: '%s'%s Share name: '%s'%s Ip address: " , - id,kref_read (&swnreg->ref_count), - swnreg->net_name, swnreg->net_name_notify ?"(y)" :"(n)" , - swnreg->share_name, swnreg->share_name_notify ?"(y)" :"(n)" ); -switch (swnreg->tcon->ses->server->dstaddr.ss_family) { + swnreg_info.id, swnreg_info.ref_count, + swnreg_info.net_name, swnreg_info.net_name_notify ?"(y)" :"(n)" , + swnreg_info.share_name, swnreg_info.share_name_notify ?"(y)" :"(n)" ); + + tcon =cifs_swn_get_tcon (&swnreg_info); +if (!tcon) { +seq_puts (m,"(no live tcon)" ); + goto next; + } + +switch (tcon->ses->server->dstaddr.ss_family) {case AF_INET : - sa = (struct sockaddr_in *) &swnreg->tcon->ses->server->dstaddr; + sa = (struct sockaddr_in *)&tcon->ses->server->dstaddr;seq_printf (m,"%pI4" , &sa->sin_addr.s_addr);break ;case AF_INET6 : - sa6 = (struct sockaddr_in6 *) &swnreg->tcon->ses->server->dstaddr; + sa6 = (struct sockaddr_in6 *)&tcon->ses->server->dstaddr;seq_printf (m,"%pI6" , &sa6->sin6_addr.s6_addr);if (sa6->sin6_scope_id)seq_printf (m,"%%%u" , sa6->sin6_scope_id);
@@ -648,23 +839,38 @@ void cifs_swn_dump(struct seq_file *m)default :seq_puts (m,"(unknown)" ); } -seq_printf (m,"%s" , swnreg->ip_notify ?"(y)" :"(n)" ); +cifs_put_tcon (tcon, netfs_trace_tcon_ref_put_swn_notify); +next: +seq_printf (m,"%s" , swnreg_info.ip_notify ?"(y)" :"(n)" ); +cifs_swn_free_reg_info (&swnreg_info); } -mutex_unlock (&cifs_swnreg_idr_mutex); +if (ret <0 ) +seq_printf (m,"\nFailed to snapshot witness registration: %d" , ret);seq_puts (m,"\n" ); }void cifs_swn_check (void ) { -struct cifs_swn_reg *swnreg; -int id; +struct cifs_swn_reg_info swnreg_info; +struct cifs_tcon *tcon; +int id =0 ;int ret; -mutex_lock (&cifs_swnreg_idr_mutex); -idr_for_each_entry (&cifs_swnreg_idr, swnreg, id) { - ret =cifs_swn_send_register_message (swnreg); +while ((ret =cifs_swn_get_next_reg_info (&id, &swnreg_info)) >0 ) { + tcon =cifs_swn_get_tcon (&swnreg_info); +if (!tcon) { +cifs_dbg (FYI ,"%s: registration id %d has no live tcon\n" , + __func__, swnreg_info.id); + goto free_info; + } + + ret =cifs_swn_send_register_message (&swnreg_info, tcon);if (ret <0 )cifs_dbg (FYI ,"%s: Failed to send register message: %d\n" , __func__, ret); +cifs_put_tcon (tcon, netfs_trace_tcon_ref_put_swn_notify); +free_info: +cifs_swn_free_reg_info (&swnreg_info); } -mutex_unlock (&cifs_swnreg_idr_mutex); +if (ret <0 ) +cifs_dbg (FYI ,"%s: Failed to snapshot registration: %d\n" , __func__, ret); }
fs/smb/client/trace.h +2 -0
@@ -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_notify, "GET SwnNot") \ 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" ) \
@@ -192,6 +193,7 @@ EM(netfs_trace_tcon_ref_put_mnt_ctx, "PUT MntCtx") \ 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_swn_notify, "PUT SwnNot") \ EM(netfs_trace_tcon_ref_put_tlink, "PUT Tlink ") \ EM(netfs_trace_tcon_ref_see_cancelled_close, "SEE Cn-Cls") \EM (netfs_trace_tcon_ref_see_fscache_collision,"SEE FV-CO!" ) \
Sasha Levin
maintainerPATCH 1/5 cifs: handle when server stops supporting multichannel
From: Shyam Prasad N <[email protected]>
[ Upstream commit ee1d21794e55ab76505745d24101331552182002 ]
When a server stops supporting multichannel, we will keep attempting reconnects to the secondary channels today. Avoid this by freeing extra channels when negotiate returns no multichannel support.
Stable-dep-of: ec457f9afe5a ("smb: client: resolve SWN tcon from live registrations")
--- fs/smb/client/cifsglob.h | 1 + fs/smb/client/cifsproto.h | 2 ++ fs/smb/client/connect.c | 10 ++++++ fs/smb/client/sess.c | 65 ++++++++++++++++++++++++++++----- fs/smb/client/smb2pdu.c | 76 ++++++++++++++++++++++++++++++++++++++- fs/smb/client/transport.c | 2 +- 6 files changed, 145 insertions(+), 11 deletions(-)
fs/smb/client/cifsglob.h +1 -0
@@ -639,6 +639,7 @@ struct TCP_Server_Info {bool noautotune;/* do not autotune send buf sizes */ bool nosharesock;bool tcp_nodelay; +bool terminate;unsigned int credits;/* send no more requests at once */ unsigned int max_credits;/* can override large 32000 default at mnt */ unsigned int in_flight;/* number of requests on the wire to server */
fs/smb/client/cifsproto.h +2 -0
@@ -662,6 +662,8 @@ cifs_chan_needs_reconnect(struct cifs_ses *ses,bool cifs_chan_is_iface_active (struct cifs_ses *ses,struct TCP_Server_Info *server); +void +cifs_disable_secondary_channels (struct cifs_ses *ses);int cifs_chan_update_iface (struct cifs_ses *ses,struct TCP_Server_Info *server);int
fs/smb/client/connect.c +10 -0
@@ -259,6 +259,14 @@ cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server,spin_lock (&cifs_tcp_ses_lock);list_for_each_entry_safe (ses, nses, &pserver->smb_ses_list, smb_ses_list) { +/* +* if channel has been marked for termination, nothing to do +* for the channel. in fact, we cannot find the channel for the +* server. So safe to exit here +*/ +if (server->terminate) +break ; +spin_lock (&ses->ses_lock);if (ses->ses_status ==SES_EXITING ) {spin_unlock (&ses->ses_lock);
@@ -302,6 +310,8 @@ cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server,spin_lock (&tcon->tc_lock); tcon->status =TID_NEED_RECON ;spin_unlock (&tcon->tc_lock); + +cancel_delayed_work (&tcon->query_interfaces); }if (ses->tcon_ipc) { ses->tcon_ipc->need_reconnect = true;
fs/smb/client/sess.c +56 -9
85 lines not on this page · raw message
fs/smb/client/smb2pdu.c +75 -1
111 lines not on this page · raw message
fs/smb/client/transport.c +1 -1
@@ -1063,7 +1063,7 @@ struct TCP_Server_Info *cifs_pick_channel(struct cifs_ses *ses)spin_lock (&ses->chan_lock);for (i =0 ; i < ses->chan_count; i++) { server = ses->chans[i].server; -if (!server) +if (!server || server->terminate)continue ;/*
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/5 cifs: after disabling multichannel, mark tcon for reconnect
From: Shyam Prasad N <[email protected]>
[ Upstream commit 27e1fd343f80168ff456785c2443136b6b7ca3cc ]
Once the server disables multichannel for an active multichannel session, on the following reconnect, the client would reduce the number of channels to 1. However, it could be the case that the tree connect was active on one of these disabled channels. This results in an unrecoverable state.
This change fixes that by making sure that whenever a channel is being terminated, the session and tcon are marked for reconnect too. This could mean a few redundant tree connect calls to the server, but considering that this is not a frequent event, we should be okay.
Stable-dep-of: ec457f9afe5a ("smb: client: resolve SWN tcon from live registrations")
--- fs/smb/client/connect.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-)
fs/smb/client/connect.c +12 -8
@@ -256,17 +256,21 @@ cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server,/* If server is a channel, select the primary channel */ pserver =CIFS_SERVER_IS_CHAN (server) ? server->primary_server : server; +/* +* if the server has been marked for termination, there is a +* chance that the remaining channels all need reconnect. To be +* on the safer side, mark the session and trees for reconnect +* for this scenario. This might cause a few redundant session +* setup and tree connect requests, but it is better than not doing +* a tree connect when needed, and all following requests failing +*/ +if (server->terminate) { + mark_smb_session = true; + server = pserver; + }spin_lock (&cifs_tcp_ses_lock);list_for_each_entry_safe (ses, nses, &pserver->smb_ses_list, smb_ses_list) { -/* -* if channel has been marked for termination, nothing to do -* for the channel. in fact, we cannot find the channel for the -* server. So safe to exit here -*/ -if (server->terminate) -break ; -spin_lock (&ses->ses_lock);if (ses->ses_status ==SES_EXITING ) {spin_unlock (&ses->ses_lock);
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 3/5 cifs: Fix reacquisition of volume cookie on still-live connection
In reply to Sasha LevinFrom: David Howells <[email protected]>
[ Upstream commit dad80c6bff770d25f67ec25fe011730e4a463008 ]
During mount, cifs_mount_get_tcon() gets a tcon resource connection record and then attaches an fscache volume cookie to it. However, it does this irrespective of whether or not the tcon returned from cifs_get_tcon() is a new record or one that's already in use. This leads to a warning about a volume cookie collision and a leaked volume cookie because tcon->fscache gets reset.
Fix this be adding a mutex and a "we've already tried this" flag and only doing it once for the lifetime of the tcon.
[!] Note: Looking at cifs_mount_get_tcon(), a more general solution may actually be required. Reacquiring the volume cookie isn't the only thing that function does: it also partially reinitialises the tcon record without any locking - which may cause live filesystem ops already using the tcon through a previous mount to malfunction.
This can be reproduced simply by something like:
mount //example.com/test /xfstest.test -o user=shares,pass=xxx,fsc mount //example.com/test /mnt -o user=shares,pass=xxx,fsc
Stable-dep-of: ec457f9afe5a ("smb: client: resolve SWN tcon from live registrations")
--- fs/smb/client/cifsglob.h | 2 ++ fs/smb/client/fscache.c | 13 +++++++++++++ fs/smb/client/misc.c | 3 +++ 3 files changed, 18 insertions(+)
fs/smb/client/cifsglob.h +2 -0
@@ -1205,7 +1205,9 @@ struct cifs_tcon {__u32 max_bytes_copy;#ifdef CONFIG_CIFS_FSCACHE u64 resource_id;/* server resource id */ +bool fscache_acquired;/* T if we've tried acquiring a cookie */ struct fscache_volume *fscache;/* cookie for share */ +struct mutex fscache_lock;/* Prevent regetting a cookie */ #endif struct list_head pending_opens;/* list of incomplete opens */ struct cached_fids *cfids;
fs/smb/client/fscache.c +13 -0
@@ -43,12 +43,23 @@ int cifs_fscache_get_super_cookie(struct cifs_tcon *tcon)char *key;int ret = -ENOMEM ; +if (tcon->fscache_acquired) +return 0 ; + +mutex_lock (&tcon->fscache_lock); +if (tcon->fscache_acquired) { +mutex_unlock (&tcon->fscache_lock); +return 0 ; + } + tcon->fscache_acquired = true; + tcon->fscache =NULL ;switch (sa->sa_family) {case AF_INET :case AF_INET6 :break ;default : +mutex_unlock (&tcon->fscache_lock);cifs_dbg (VFS ,"Unknown network family '%d'\n" , sa->sa_family);return -EINVAL ; }
@@ -57,6 +68,7 @@ int cifs_fscache_get_super_cookie(struct cifs_tcon *tcon) sharename =extract_sharename (tcon->tree_name);if (IS_ERR (sharename)) { +mutex_unlock (&tcon->fscache_lock);cifs_dbg (FYI ,"%s: couldn't extract sharename\n" , __func__);return PTR_ERR (sharename); }
@@ -90,6 +102,7 @@ int cifs_fscache_get_super_cookie(struct cifs_tcon *tcon)kfree (key); out:kfree (sharename); +mutex_unlock (&tcon->fscache_lock);return ret; }
fs/smb/client/misc.c +3 -0
@@ -139,6 +139,9 @@ tconInfoAlloc(void)spin_lock_init (&ret_buf->stat_lock);atomic_set (&ret_buf->num_local_opens,0 );atomic_set (&ret_buf->num_remote_opens,0 ); +#ifdef CONFIG_CIFS_FSCACHE +mutex_init (&ret_buf->fscache_lock); +#endif return ret_buf; }
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 4/5 cifs: Add tracing for the cifs_tcon struct refcounting
In reply to Sasha LevinFrom: David Howells <[email protected]>
[ Upstream commit afc23febd51c7e24361e3a9c09f3e892eb0a41ea ]
Add tracing for the refcounting/lifecycle of the cifs_tcon struct, marking different events with different labels and giving each tcon its own debug ID so that the tracelines corresponding to individual tcons can be distinguished. This can be enabled with:
echo 1 >/sys/kernel/debug/tracing/events/cifs/smb3_tcon_ref/enable
Stable-dep-of: ec457f9afe5a ("smb: client: resolve SWN tcon from live registrations")
--- fs/smb/client/cifs_swn.c | 13 +++-- fs/smb/client/cifsfs.c | 2 + fs/smb/client/cifsglob.h | 1 + fs/smb/client/cifsproto.h | 8 ++- fs/smb/client/connect.c | 21 ++++---- fs/smb/client/dfs_cache.c | 2 +- fs/smb/client/fscache.c | 7 +++ fs/smb/client/misc.c | 12 +++-- fs/smb/client/smb2misc.c | 10 ++-- fs/smb/client/smb2ops.c | 7 ++- fs/smb/client/smb2pdu.c | 8 +-- fs/smb/client/smb2transport.c | 2 + fs/smb/client/trace.h | 93 ++++++++++++++++++++++++++++++++++- 13 files changed, 151 insertions(+), 35 deletions(-)
fs/smb/client/cifs_swn.c +6 -7
@@ -313,17 +313,15 @@ static struct cifs_swn_reg *cifs_get_swn_reg(struct cifs_tcon *tcon) reg =cifs_find_swn_reg (tcon);if (!IS_ERR (reg)) {kref_get (®->ref_count); -mutex_unlock (&cifs_swnreg_idr_mutex); -return reg; + goto unlock; }else if (PTR_ERR (reg) != -EEXIST ) { -mutex_unlock (&cifs_swnreg_idr_mutex); -return reg; + goto unlock; } reg =kmalloc (sizeof (struct cifs_swn_reg ),GFP_ATOMIC );if (reg ==NULL ) { -mutex_unlock (&cifs_swnreg_idr_mutex); -return ERR_PTR (-ENOMEM ); + ret = -ENOMEM ; + goto fail_unlock; }kref_init (®->ref_count);
@@ -354,7 +352,7 @@ static struct cifs_swn_reg *cifs_get_swn_reg(struct cifs_tcon *tcon) reg->ip_notify = (tcon->capabilities &SMB2_SHARE_CAP_SCALEOUT ); reg->tcon = tcon; - +unlock:mutex_unlock (&cifs_swnreg_idr_mutex);return reg;
@@ -365,6 +363,7 @@ static struct cifs_swn_reg *cifs_get_swn_reg(struct cifs_tcon *tcon)idr_remove (&cifs_swnreg_idr, reg->id); fail:kfree (reg); +fail_unlock:mutex_unlock (&cifs_swnreg_idr_mutex);return ERR_PTR (ret); }
fs/smb/client/cifsfs.c +2 -0
@@ -747,6 +747,8 @@ static void cifs_umount_begin(struct super_block *sb)spin_lock (&cifs_tcp_ses_lock);spin_lock (&tcon->tc_lock); +trace_smb3_tcon_ref (tcon->debug_id, tcon->tc_count, + netfs_trace_tcon_ref_see_umount);if ((tcon->tc_count >1 ) || (tcon->status ==TID_EXITING )) {/* we have other mounts to same share or we have already tried to umount this and woken up
fs/smb/client/cifsglob.h +1 -0
@@ -1121,6 +1121,7 @@ struct cifs_fattr {*/ struct cifs_tcon {struct list_head tcon_list; +int debug_id;/* Debugging for tracing */ int tc_count;struct list_head rlist;/* reconnect list */ spinlock_t tc_lock;/* protect anything here that is not protected */
fs/smb/client/cifsproto.h +3 -5
@@ -305,7 +305,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx, struct TCP_Server_Info *primary_server);extern void cifs_put_tcp_session (struct TCP_Server_Info *server,int from_reconnect); -extern void cifs_put_tcon (struct cifs_tcon *tcon); +extern void cifs_put_tcon (struct cifs_tcon *tcon,enum smb3_tcon_ref_trace trace);#if IS_ENABLED (CONFIG_CIFS_DFS_UPCALL )extern void cifs_dfs_release_automount_timer (void );
@@ -525,8 +525,8 @@ extern int CIFSSMBLogoff(const unsigned int xid, struct cifs_ses *ses);extern struct cifs_ses *sesInfoAlloc (void );extern void sesInfoFree (struct cifs_ses *); -extern struct cifs_tcon *tconInfoAlloc (void ); -extern void tconInfoFree (struct cifs_tcon *); +extern struct cifs_tcon *tconInfoAlloc (enum smb3_tcon_ref_trace trace); +extern void tconInfoFree (struct cifs_tcon *tcon,enum smb3_tcon_ref_trace trace);extern int cifs_sign_rqst (struct smb_rqst *rqst,struct TCP_Server_Info *server,__u32 *pexpected_response_sequence_number);
@@ -719,8 +719,6 @@ static inline int cifs_create_options(struct cifs_sb_info *cifs_sb, int options)return options; } -struct super_block *cifs_get_tcon_super (struct cifs_tcon *tcon); -void cifs_put_tcon_super (struct super_block *sb);int cifs_wait_for_server_reconnect (struct TCP_Server_Info *server,bool retry);static inline void release_mid (struct mid_q_entry *mid)
fs/smb/client/connect.c +12 -9
@@ -1985,7 +1985,7 @@ cifs_setup_ipc(struct cifs_ses *ses, struct smb3_fs_context *ctx) } } - tcon =tconInfoAlloc (); + tcon =tconInfoAlloc (netfs_trace_tcon_ref_new_ipc);if (tcon ==NULL )return -ENOMEM ;
@@ -2002,7 +2002,7 @@ cifs_setup_ipc(struct cifs_ses *ses, struct smb3_fs_context *ctx)if (rc) {cifs_server_dbg (VFS ,"failed to connect to IPC (rc=%d)\n" , rc); -tconInfoFree (tcon); +tconInfoFree (tcon, netfs_trace_tcon_ref_free_ipc_fail); goto out; }
@@ -2085,7 +2085,7 @@ void cifs_put_smb_ses(struct cifs_ses *ses)* files on session close, as specified in MS-SMB2 3.3.5.6 Receiving an * SMB2 LOGOFF Request. */ -tconInfoFree (tcon); +tconInfoFree (tcon, netfs_trace_tcon_ref_free_ipc);if (do_logoff) { xid =get_xid (); rc = server->ops->logoff (xid, ses);
@@ -2459,6 +2459,8 @@ cifs_find_tcon(struct cifs_ses *ses, struct smb3_fs_context *ctx)continue ; } ++tcon->tc_count; +trace_smb3_tcon_ref (tcon->debug_id, tcon->tc_count, + netfs_trace_tcon_ref_get_find);spin_unlock (&tcon->tc_lock);spin_unlock (&cifs_tcp_ses_lock);return tcon;
@@ -2468,7 +2470,7 @@ cifs_find_tcon(struct cifs_ses *ses, struct smb3_fs_context *ctx) }void -cifs_put_tcon (struct cifs_tcon *tcon) +cifs_put_tcon (struct cifs_tcon *tcon,enum smb3_tcon_ref_trace trace) {unsigned int xid;struct cifs_ses *ses;
@@ -2484,6 +2486,7 @@ cifs_put_tcon(struct cifs_tcon *tcon)cifs_dbg (FYI ,"%s: tc_count=%d\n" , __func__, tcon->tc_count);spin_lock (&cifs_tcp_ses_lock);spin_lock (&tcon->tc_lock); +trace_smb3_tcon_ref (tcon->debug_id, tcon->tc_count -1 , trace);if (--tcon->tc_count >0 ) {spin_unlock (&tcon->tc_lock);spin_unlock (&cifs_tcp_ses_lock);
@@ -2517,7 +2520,7 @@ cifs_put_tcon(struct cifs_tcon *tcon)_free_xid (xid);cifs_fscache_release_super_cookie (tcon); -tconInfoFree (tcon); +tconInfoFree (tcon, netfs_trace_tcon_ref_free);cifs_put_smb_ses (ses); }
@@ -2565,7 +2568,7 @@ cifs_get_tcon(struct cifs_ses *ses, struct smb3_fs_context *ctx) goto out_fail; } - tcon =tconInfoAlloc (); + tcon =tconInfoAlloc (netfs_trace_tcon_ref_new);if (tcon ==NULL ) { rc = -ENOMEM ; goto out_fail;
@@ -2762,7 +2765,7 @@ cifs_get_tcon(struct cifs_ses *ses, struct smb3_fs_context *ctx)return tcon; out_fail: -tconInfoFree (tcon); +tconInfoFree (tcon, netfs_trace_tcon_ref_free_fail);return ERR_PTR (rc); }
@@ -2779,7 +2782,7 @@ cifs_put_tlink(struct tcon_link *tlink) }if (!IS_ERR (tlink_tcon (tlink))) -cifs_put_tcon (tlink_tcon (tlink)); +cifs_put_tcon (tlink_tcon (tlink), netfs_trace_tcon_ref_put_tlink);kfree (tlink);return ; }
@@ -3338,7 +3341,7 @@ static inline void mount_put_conns(struct mount_ctx *mnt_ctx)int rc =0 ;if (mnt_ctx->tcon) -cifs_put_tcon (mnt_ctx->tcon); +cifs_put_tcon (mnt_ctx->tcon, netfs_trace_tcon_ref_put_mnt_ctx);else if (mnt_ctx->ses)cifs_put_smb_ses (mnt_ctx->ses);else if (mnt_ctx->server)
fs/smb/client/dfs_cache.c +1 -1
@@ -1564,7 +1564,7 @@ static void refresh_mounts(struct cifs_ses **sessions) }mutex_unlock (&server->refpath_lock); -cifs_put_tcon (tcon); +cifs_put_tcon (tcon, netfs_trace_tcon_ref_put_dfs_refer); } }
fs/smb/client/fscache.c +7 -0
@@ -94,6 +94,11 @@ int cifs_fscache_get_super_cookie(struct cifs_tcon *tcon) }pr_err ("Cache volume key already in use (%s)\n" , key); vcookie =NULL ; +trace_smb3_tcon_ref (tcon->debug_id, tcon->tc_count, + netfs_trace_tcon_ref_see_fscache_collision); + }else { +trace_smb3_tcon_ref (tcon->debug_id, tcon->tc_count, + netfs_trace_tcon_ref_see_fscache_okay); } tcon->fscache = vcookie;
@@ -115,6 +120,8 @@ void cifs_fscache_release_super_cookie(struct cifs_tcon *tcon)cifs_fscache_fill_volume_coherency (tcon, &cd);fscache_relinquish_volume (tcon->fscache, &cd, false); tcon->fscache =NULL ; +trace_smb3_tcon_ref (tcon->debug_id, tcon->tc_count, + netfs_trace_tcon_ref_see_fscache_relinq); }void cifs_fscache_get_inode_cookie (struct inode *inode)
fs/smb/client/misc.c +8 -4
@@ -116,9 +116,10 @@ sesInfoFree(struct cifs_ses *buf_to_free) }struct cifs_tcon * -tconInfoAlloc (void ) +tconInfoAlloc (enum smb3_tcon_ref_trace trace) {struct cifs_tcon *ret_buf; +static atomic_t tcon_debug_id; ret_buf =kzalloc (sizeof (*ret_buf),GFP_KERNEL );if (!ret_buf)
@@ -131,7 +132,8 @@ tconInfoAlloc(void)atomic_inc (&tconInfoAllocCount); ret_buf->status =TID_NEW ; - ++ret_buf->tc_count; + ret_buf->debug_id =atomic_inc_return (&tcon_debug_id); + ret_buf->tc_count =1 ;spin_lock_init (&ret_buf->tc_lock);INIT_LIST_HEAD (&ret_buf->openFileList);INIT_LIST_HEAD (&ret_buf->tcon_list);
@@ -142,17 +144,19 @@ tconInfoAlloc(void)#ifdef CONFIG_CIFS_FSCACHE mutex_init (&ret_buf->fscache_lock);#endif +trace_smb3_tcon_ref (ret_buf->debug_id, ret_buf->tc_count, trace);return ret_buf; }void -tconInfoFree (struct cifs_tcon *tcon) +tconInfoFree (struct cifs_tcon *tcon,enum smb3_tcon_ref_trace trace) {if (tcon ==NULL ) {cifs_dbg (FYI ,"Null buffer passed to tconInfoFree\n" );return ; } +trace_smb3_tcon_ref (tcon->debug_id, tcon->tc_count, trace);free_cached_dirs (tcon->cfids);atomic_dec (&tconInfoAllocCount);kfree (tcon->nativeFileSystem);
@@ -848,7 +852,7 @@ void cifs_close_all_deferred_files_sb(struct cifs_sb_info *cifs_sb)list_for_each_entry_safe (tmp_list, q, &tcon_head, entry) {cifs_close_all_deferred_files (tmp_list->tcon);list_del (&tmp_list->entry); -cifs_put_tcon (tmp_list->tcon); +cifs_put_tcon (tmp_list->tcon, netfs_trace_tcon_ref_put_tlink);kfree (tmp_list); } }
fs/smb/client/smb2misc.c +7 -3
@@ -768,7 +768,7 @@ smb2_cancelled_close_fid(struct work_struct *work)if (rc)cifs_tcon_dbg (VFS ,"Close cancelled mid failed rc:%d\n" , rc); -cifs_put_tcon (tcon); +cifs_put_tcon (tcon, netfs_trace_tcon_ref_put_cancelled_close_fid);kfree (cancelled); }
@@ -812,6 +812,8 @@ smb2_handle_cancelled_close(struct cifs_tcon *tcon, __u64 persistent_fid,if (tcon->tc_count <=0 ) {struct TCP_Server_Info *server =NULL ; +trace_smb3_tcon_ref (tcon->debug_id, tcon->tc_count, + netfs_trace_tcon_ref_see_cancelled_close);WARN_ONCE (tcon->tc_count <0 ,"tcon refcount is negative" );spin_unlock (&cifs_tcp_ses_lock);
@@ -825,12 +827,14 @@ smb2_handle_cancelled_close(struct cifs_tcon *tcon, __u64 persistent_fid,return 0 ; } tcon->tc_count++; +trace_smb3_tcon_ref (tcon->debug_id, tcon->tc_count, + netfs_trace_tcon_ref_get_cancelled_close);spin_unlock (&cifs_tcp_ses_lock); rc =__smb2_handle_cancelled_cmd (tcon,SMB2_CLOSE_HE ,0 , persistent_fid, volatile_fid);if (rc) -cifs_put_tcon (tcon); +cifs_put_tcon (tcon, netfs_trace_tcon_ref_put_cancelled_close);return rc; }
@@ -858,7 +862,7 @@ smb2_handle_cancelled_mid(struct mid_q_entry *mid, struct TCP_Server_Info *serve rsp->PersistentFileId, rsp->VolatileFileId);if (rc) -cifs_put_tcon (tcon); +cifs_put_tcon (tcon, netfs_trace_tcon_ref_put_cancelled_mid);return rc; }
fs/smb/client/smb2ops.c +6 -1
@@ -2847,8 +2847,11 @@ smb2_get_dfs_refer(const unsigned int xid, struct cifs_ses *ses, tcon =list_first_entry_or_null (&ses->tcon_list, struct cifs_tcon, tcon_list); -if (tcon) +if (tcon) { tcon->tc_count++; +trace_smb3_tcon_ref (tcon->debug_id, tcon->tc_count, + netfs_trace_tcon_ref_get_dfs_refer); + }spin_unlock (&cifs_tcp_ses_lock); }
@@ -2912,6 +2915,8 @@ smb2_get_dfs_refer(const unsigned int xid, struct cifs_ses *ses,/* ipc tcons are not refcounted */ spin_lock (&cifs_tcp_ses_lock); tcon->tc_count--; +trace_smb3_tcon_ref (tcon->debug_id, tcon->tc_count, + netfs_trace_tcon_ref_dec_dfs_refer);/* tc_count can never go negative */ WARN_ON (tcon->tc_count <0 );spin_unlock (&cifs_tcp_ses_lock);
fs/smb/client/smb2pdu.c +5 -3
@@ -3995,6 +3995,8 @@ void smb2_reconnect_server(struct work_struct *work)list_for_each_entry (tcon, &ses->tcon_list, tcon_list) {if (tcon->need_reconnect || tcon->need_reopen_files) { tcon->tc_count++; +trace_smb3_tcon_ref (tcon->debug_id, tcon->tc_count, + netfs_trace_tcon_ref_get_reconnect_server);list_add_tail (&tcon->rlist, &tmp_list); tcon_selected = tcon_exist = true; }
@@ -4040,14 +4042,14 @@ void smb2_reconnect_server(struct work_struct *work)if (tcon->ipc)cifs_put_smb_ses (tcon->ses);else -cifs_put_tcon (tcon); +cifs_put_tcon (tcon, netfs_trace_tcon_ref_put_reconnect_server); }if (!ses_exist) goto done;/* allocate a dummy tcon struct used for reconnect */ - tcon =tconInfoAlloc (); + tcon =tconInfoAlloc (netfs_trace_tcon_ref_new_reconnect_server);if (!tcon) { resched = true;list_for_each_entry_safe (ses, ses2, &tmp_ses_list, rlist) {
@@ -4070,7 +4072,7 @@ void smb2_reconnect_server(struct work_struct *work)list_del_init (&ses->rlist);cifs_put_smb_ses (ses); } -tconInfoFree (tcon); +tconInfoFree (tcon, netfs_trace_tcon_ref_free_reconnect_server); done:cifs_dbg (FYI ,"Reconnecting tcons and channels finished\n" );
fs/smb/client/smb2transport.c +2 -0
@@ -189,6 +189,8 @@ smb2_find_smb_sess_tcon_unlocked(struct cifs_ses *ses, __u32 tid)if (tcon->tid != tid)continue ; ++tcon->tc_count; +trace_smb3_tcon_ref (tcon->debug_id, tcon->tc_count, + netfs_trace_tcon_ref_get_find_sess_tcon);return tcon; }
fs/smb/client/trace.h +91 -2
112 lines not on this page · raw message
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 5/5 smb: client: resolve SWN tcon from live registrations
In reply to Sasha LevinFrom: Michael Bommarito <[email protected]>
[ Upstream commit ec457f9afe5ae9538bdcd58fd4cb442b9787e183 ]
cifs_swn_notify() looks up a witness registration by id under cifs_swnreg_idr_mutex, drops the mutex, and then uses the registration's cached tcon pointer. That pointer is not a lifetime reference, and it is not a stable representative once cifs_get_swn_reg() lets multiple tcons for the same net/share name share one registration id.
A same-share second mount can keep the cifs_swn_reg alive after the first tcon unregisters and is freed. The registration then still points at the freed first tcon, so taking tc_lock or incrementing tc_count through swnreg->tcon only moves the use-after-free earlier. Taking tc_lock while holding cifs_swnreg_idr_mutex also violates the documented CIFS lock order.
Fix this by making the registration store only the stable witness
identity: id, net name, share name, and notify flags. When a notify arrives, copy that identity under cifs_swnreg_idr_mutex, drop the mutex, then find and pin a live witness tcon that currently matches the net/share pair under the normal cifs_tcp_ses_lock -> tc_lock order. The notification path uses that pinned tcon directly and drops the reference when done.
Registration and unregister messages now use the live tcon passed by the caller instead of a cached tcon in the registration. The final unregister send is folded into cifs_swn_unregister() while the registration is still protected by cifs_swnreg_idr_mutex. This removes the previous find/drop/reacquire raw-pointer window. The release path only removes the idr entry and frees the stable identity strings.
This preserves the intended one-registration/many-tcon behavior: a registration id represents a net/share pair, and notify handling acts on a live representative selected at use time. It also preserves CLIENT_MOVE ordering for the representative tcon because the old-IP unregister is sent before cifs_swn_register() sends the new-IP register.
--- fs/smb/client/cifs_swn.c | 314 ++++++++++++++++++++++++++++++++------- fs/smb/client/trace.h | 2 + 2 files changed, 262 insertions(+), 54 deletions(-)
fs/smb/client/cifs_swn.c +260 -54
@@ -28,10 +28,54 @@ struct cifs_swn_reg {bool net_name_notify;bool share_name_notify;bool ip_notify; +}; -struct cifs_tcon *tcon; +struct cifs_swn_reg_info { +int id; +unsigned int ref_count; +const char *net_name; +const char *share_name; +bool net_name_notify; +bool share_name_notify; +bool ip_notify; }; +static void cifs_swn_snapshot_reg (struct cifs_swn_reg *swnreg, +struct cifs_swn_reg_info *info) +{ + info->id = swnreg->id; + info->ref_count =kref_read (&swnreg->ref_count); + info->net_name = swnreg->net_name; + info->share_name = swnreg->share_name; + info->net_name_notify = swnreg->net_name_notify; + info->share_name_notify = swnreg->share_name_notify; + info->ip_notify = swnreg->ip_notify; +} + +static int cifs_swn_dup_reg (struct cifs_swn_reg *swnreg, +struct cifs_swn_reg_info *info) +{ +cifs_swn_snapshot_reg (swnreg, info); + + info->net_name =kstrdup (swnreg->net_name,GFP_KERNEL ); +if (!info->net_name) +return -ENOMEM ; + + info->share_name =kstrdup (swnreg->share_name,GFP_KERNEL ); +if (!info->share_name) { +kfree (info->net_name); +return -ENOMEM ; + } + +return 0 ; +} + +static void cifs_swn_free_reg_info (struct cifs_swn_reg_info *info) +{ +kfree (info->net_name); +kfree (info->share_name); +} +static int cifs_swn_auth_info_krb (struct cifs_tcon *tcon,struct sk_buff *skb) {int ret;
@@ -73,7 +117,8 @@ static int cifs_swn_auth_info_ntlm(struct cifs_tcon *tcon, struct sk_buff *skb)* 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) +static int cifs_swn_send_register_message (struct cifs_swn_reg_info *swnreg, +struct cifs_tcon *tcon) {struct sk_buff *skb;struct genlmsghdr *hdr;
@@ -111,10 +156,10 @@ static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg)* told to switch to it (client move message). In these cases we unregister from the * server address and register to the new address when we receive the notification. */ -if (swnreg->tcon->ses->server->use_swn_dstaddr) - addr = &swnreg->tcon->ses->server->swn_dstaddr; +if (tcon->ses->server->use_swn_dstaddr) + addr = &tcon->ses->server->swn_dstaddr;else - addr = &swnreg->tcon->ses->server->dstaddr; + addr = &tcon->ses->server->dstaddr; ret =nla_put (skb,CIFS_GENL_ATTR_SWN_IP ,sizeof (struct sockaddr_storage ), addr);if (ret <0 )
@@ -138,10 +183,10 @@ static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg) goto nlmsg_fail; } - authtype =cifs_select_sectype (swnreg->tcon->ses->server, swnreg->tcon->ses->sectype); + authtype =cifs_select_sectype (tcon->ses->server, tcon->ses->sectype);switch (authtype) {case Kerberos: - ret =cifs_swn_auth_info_krb (swnreg->tcon, skb); + ret =cifs_swn_auth_info_krb (tcon, skb);if (ret <0 ) {cifs_dbg (VFS ,"%s: Failed to get kerberos auth info: %d\n" , __func__, ret); goto nlmsg_fail;
@@ -149,7 +194,7 @@ static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg)break ;case NTLMv2:case RawNTLMSSP: - ret =cifs_swn_auth_info_ntlm (swnreg->tcon, skb); + ret =cifs_swn_auth_info_ntlm (tcon, skb);if (ret <0 ) {cifs_dbg (VFS ,"%s: Failed to get NTLM auth info: %d\n" , __func__, ret); goto nlmsg_fail;
@@ -179,7 +224,8 @@ static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg)/* * Sends an uregister message to the userspace daemon based on the registration */ -static int cifs_swn_send_unregister_message (struct cifs_swn_reg *swnreg) +static int cifs_swn_send_unregister_message (struct cifs_swn_reg_info *swnreg, +struct cifs_tcon *tcon) {struct sk_buff *skb;struct genlmsghdr *hdr;
@@ -208,7 +254,7 @@ static int cifs_swn_send_unregister_message(struct cifs_swn_reg *swnreg) goto nlmsg_fail; ret =nla_put (skb,CIFS_GENL_ATTR_SWN_IP ,sizeof (struct sockaddr_storage ), - &swnreg->tcon->ses->server->dstaddr); + &tcon->ses->server->dstaddr);if (ret <0 ) goto nlmsg_fail;
@@ -244,6 +290,88 @@ static int cifs_swn_send_unregister_message(struct cifs_swn_reg *swnreg)return ret; } +/* +* Allocation-free mirror of extract_hostname() + extract_sharename() from +* fs/smb/client/unc.c. Those helpers kmalloc(GFP_KERNEL); this runs under +* cifs_tcp_ses_lock and tcon->tc_lock, both spinlocks, so we mirror their +* parsing in place against the caller's stable net_name/share_name strings. +* Keep in sync with unc.c. +*/ +static bool cifs_swn_tcon_matches (struct cifs_tcon *tcon, +const char *net_name, +const char *share_name) +{ +const char *unc = tcon->tree_name; +const char *host, *share, *delim; +size_t host_len, share_len; + +if (!tcon->use_witness) +return false; + +/* extract_hostname: require strlen(unc) >= 3 */ +if (strnlen (unc,3 ) <3 ) +return false; +/* extract_hostname: skip all leading '\' characters */ +for (host = unc; *host =='\\' ; host++) + ; +if (!*host) +return false; + delim =strchr (host,'\\' ); +if (!delim) +return false; + host_len = delim - host; +if (strlen (net_name) != host_len || +strncasecmp (host, net_name, host_len)) +return false; + +/* extract_sharename: start at unc + 2, then first '\' onward */ + share = unc +2 ; + delim =strchr (share,'\\' ); +if (!delim) +return false; + share = delim +1 ; + share_len =strlen (share); + +return strlen (share_name) == share_len && + !strncasecmp (share, share_name, share_len); +} + +/* +* One SWN registration id represents one net/share name pair. Multiple +* mounted tcons can therefore share the id. Pick a live representative at +* use time instead of caching the first tcon pointer in the registration. +*/ +static struct cifs_tcon *cifs_swn_get_tcon (struct cifs_swn_reg_info *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) { +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_tcon_matches (tcon, swnreg->net_name, + swnreg->share_name)) { +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_notify); +spin_unlock (&tcon->tc_lock); +spin_unlock (&cifs_tcp_ses_lock); +return tcon; + } + } + } +spin_unlock (&cifs_tcp_ses_lock); +return NULL ; +} +/* * Try to find a matching registration for the tcon's server name and share name. * Calls to this function must be protected by cifs_swnreg_idr_mutex.
@@ -350,8 +478,6 @@ static struct cifs_swn_reg *cifs_get_swn_reg(struct cifs_tcon *tcon) reg->net_name_notify = true; reg->share_name_notify = true; reg->ip_notify = (tcon->capabilities &SMB2_SHARE_CAP_SCALEOUT ); - - reg->tcon = tcon; unlock:mutex_unlock (&cifs_swnreg_idr_mutex);
@@ -371,11 +497,6 @@ static struct cifs_swn_reg *cifs_get_swn_reg(struct cifs_tcon *tcon)static void cifs_swn_reg_release (struct kref *ref) { struct cifs_swn_reg *swnreg = container_of(ref, struct cifs_swn_reg, ref_count); -int ret; - - ret =cifs_swn_send_unregister_message (swnreg); -if (ret <0 ) -cifs_dbg (VFS ,"%s: Failed to send unregister message: %d\n" , __func__, ret);idr_remove (&cifs_swnreg_idr, swnreg->id);kfree (swnreg->net_name);
@@ -383,23 +504,33 @@ static void cifs_swn_reg_release(struct kref *ref)kfree (swnreg); } -static void cifs_put_swn_reg (struct cifs_swn_reg *swnreg) +static void cifs_put_swn_reg_locked (struct cifs_swn_reg *swnreg, +struct cifs_tcon *tcon) { -mutex_lock (&cifs_swnreg_idr_mutex); +if (kref_read (&swnreg->ref_count) ==1 ) { +struct cifs_swn_reg_info swnreg_info; +int ret; + +cifs_swn_snapshot_reg (swnreg, &swnreg_info); + ret =cifs_swn_send_unregister_message (&swnreg_info, tcon); +if (ret <0 ) +cifs_dbg (VFS ,"%s: Failed to send unregister message: %d\n" , + __func__, ret); + } +kref_put (&swnreg->ref_count, cifs_swn_reg_release); -mutex_unlock (&cifs_swnreg_idr_mutex); } -static int cifs_swn_resource_state_changed (struct cifs_swn_reg *swnreg,const char *name,int state) +static int cifs_swn_resource_state_changed (struct cifs_tcon *tcon,const char *name,int state) {switch (state) {case CIFS_SWN_RESOURCE_STATE_UNAVAILABLE :cifs_dbg (FYI ,"%s: resource name '%s' become unavailable\n" , __func__, name); -cifs_signal_cifsd_for_reconnect (swnreg->tcon->ses->server, true); +cifs_signal_cifsd_for_reconnect (tcon->ses->server, true);break ;case CIFS_SWN_RESOURCE_STATE_AVAILABLE :cifs_dbg (FYI ,"%s: resource name '%s' become available\n" , __func__, name); -cifs_signal_cifsd_for_reconnect (swnreg->tcon->ses->server, true); +cifs_signal_cifsd_for_reconnect (tcon->ses->server, true);break ;case CIFS_SWN_RESOURCE_STATE_UNKNOWN :cifs_dbg (FYI ,"%s: resource name '%s' changed to unknown state\n" , __func__, name);
@@ -505,7 +636,7 @@ static int cifs_swn_reconnect(struct cifs_tcon *tcon, struct sockaddr_storage *areturn ret; } -static int cifs_swn_client_move (struct cifs_swn_reg *swnreg,struct sockaddr_storage *addr) +static int cifs_swn_client_move (struct cifs_tcon *tcon,struct sockaddr_storage *addr) {struct sockaddr_in *ipv4 = (struct sockaddr_in *)addr;struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)addr;
@@ -515,14 +646,17 @@ static int cifs_swn_client_move(struct cifs_swn_reg *swnreg, struct sockaddr_stoelse if (addr->ss_family ==AF_INET6 )cifs_dbg (FYI ,"%s: move to %pI6\n" , __func__, &ipv6->sin6_addr); -return cifs_swn_reconnect (swnreg->tcon, addr); +return cifs_swn_reconnect (tcon, addr); }int cifs_swn_notify (struct sk_buff *skb,struct genl_info *info) {struct cifs_swn_reg *swnreg; +struct cifs_swn_reg_info swnreg_info; +struct cifs_tcon *tcon;char name[256 ];int type; +int ret =0 ;if (info->attrs[CIFS_GENL_ATTR_SWN_REGISTRATION_ID ]) {int swnreg_id;
@@ -530,21 +664,34 @@ int cifs_swn_notify(struct sk_buff *skb, struct genl_info *info) swnreg_id =nla_get_u32 (info->attrs[CIFS_GENL_ATTR_SWN_REGISTRATION_ID ]);mutex_lock (&cifs_swnreg_idr_mutex); swnreg =idr_find (&cifs_swnreg_idr, swnreg_id); -mutex_unlock (&cifs_swnreg_idr_mutex);if (swnreg ==NULL ) { +mutex_unlock (&cifs_swnreg_idr_mutex);cifs_dbg (FYI ,"%s: registration id %d not found\n" , __func__, swnreg_id);return -EINVAL ; } + ret =cifs_swn_dup_reg (swnreg, &swnreg_info); +mutex_unlock (&cifs_swnreg_idr_mutex); +if (ret) +return ret; }else {cifs_dbg (FYI ,"%s: missing registration id attribute\n" , __func__);return -EINVAL ; } + tcon =cifs_swn_get_tcon (&swnreg_info); +if (!tcon) { +cifs_dbg (FYI ,"%s: registration id %d has no live tcon\n" , + __func__, swnreg_info.id); + ret = -ENODEV ; + goto free_info; + } +if (info->attrs[CIFS_GENL_ATTR_SWN_NOTIFICATION_TYPE ]) { type =nla_get_u32 (info->attrs[CIFS_GENL_ATTR_SWN_NOTIFICATION_TYPE ]); }else {cifs_dbg (FYI ,"%s: missing notification type attribute\n" , __func__); -return -EINVAL ; + ret = -EINVAL ; + goto out; }switch (type) {
@@ -556,15 +703,18 @@ int cifs_swn_notify(struct sk_buff *skb, struct genl_info *info) sizeof(name)); }else {cifs_dbg (FYI ,"%s: missing resource name attribute\n" , __func__); -return -EINVAL ; + ret = -EINVAL ; + goto out; }if (info->attrs[CIFS_GENL_ATTR_SWN_RESOURCE_STATE ]) { state =nla_get_u32 (info->attrs[CIFS_GENL_ATTR_SWN_RESOURCE_STATE ]); }else {cifs_dbg (FYI ,"%s: missing resource state attribute\n" , __func__); -return -EINVAL ; + ret = -EINVAL ; + goto out; } -return cifs_swn_resource_state_changed (swnreg, name, state); + ret =cifs_swn_resource_state_changed (tcon, name, state); +break ; }case CIFS_SWN_NOTIFICATION_CLIENT_MOVE : {struct sockaddr_storage addr;
@@ -573,28 +723,36 @@ int cifs_swn_notify(struct sk_buff *skb, struct genl_info *info)nla_memcpy (&addr, info->attrs[CIFS_GENL_ATTR_SWN_IP ],sizeof (addr)); }else {cifs_dbg (FYI ,"%s: missing IP address attribute\n" , __func__); -return -EINVAL ; + ret = -EINVAL ; + goto out; } -return cifs_swn_client_move (swnreg, &addr); + ret =cifs_swn_client_move (tcon, &addr); +break ; }default :cifs_dbg (FYI ,"%s: unknown notification type %d\n" , __func__, type);break ; } -return 0 ; +out: +cifs_put_tcon (tcon, netfs_trace_tcon_ref_put_swn_notify); +free_info: +cifs_swn_free_reg_info (&swnreg_info); +return ret; }int cifs_swn_register (struct cifs_tcon *tcon) {struct cifs_swn_reg *swnreg; +struct cifs_swn_reg_info swnreg_info;int ret; swnreg =cifs_get_swn_reg (tcon);if (IS_ERR (swnreg))return PTR_ERR (swnreg); - ret =cifs_swn_send_register_message (swnreg); +cifs_swn_snapshot_reg (swnreg, &swnreg_info); + ret =cifs_swn_send_register_message (&swnreg_info, tcon);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 echo task will retry */
@@ -615,35 +773,68 @@ int cifs_swn_unregister(struct cifs_tcon *tcon)return PTR_ERR (swnreg); } +cifs_put_swn_reg_locked (swnreg, tcon);mutex_unlock (&cifs_swnreg_idr_mutex); -cifs_put_swn_reg (swnreg); -return 0 ; } -void cifs_swn_dump (struct seq_file *m) +/* +* Snapshot one registration under cifs_swnreg_idr_mutex and return. Callers +* intentionally do the per-registration network/genlmsg work without the +* mutex held, both to keep the critical section short and to avoid nesting +* cifs_swnreg_idr_mutex inside the higher tc_lock when a live tcon is then +* pinned for the send. +*/ +static int cifs_swn_get_next_reg_info (int *id,struct cifs_swn_reg_info *info) {struct cifs_swn_reg *swnreg; +int ret =0 ; + +mutex_lock (&cifs_swnreg_idr_mutex); + swnreg =idr_get_next (&cifs_swnreg_idr, id); +if (swnreg) { + ret =cifs_swn_dup_reg (swnreg, info); +if (!ret) { + *id = swnreg->id +1 ; + ret =1 ; + } + } +mutex_unlock (&cifs_swnreg_idr_mutex); + +return ret; +} + +void cifs_swn_dump (struct seq_file *m) +{ +struct cifs_swn_reg_info swnreg_info; +struct cifs_tcon *tcon;struct sockaddr_in *sa;struct sockaddr_in6 *sa6; -int id; +int id =0 ; +int ret;seq_puts (m,"Witness registrations:" ); -mutex_lock (&cifs_swnreg_idr_mutex); -idr_for_each_entry (&cifs_swnreg_idr, swnreg, id) { +while ((ret =cifs_swn_get_next_reg_info (&id, &swnreg_info)) >0 ) {seq_printf (m,"\nId: %u Refs: %u Network name: '%s'%s Share name: '%s'%s Ip address: " , - id,kref_read (&swnreg->ref_count), - swnreg->net_name, swnreg->net_name_notify ?"(y)" :"(n)" , - swnreg->share_name, swnreg->share_name_notify ?"(y)" :"(n)" ); -switch (swnreg->tcon->ses->server->dstaddr.ss_family) { + swnreg_info.id, swnreg_info.ref_count, + swnreg_info.net_name, swnreg_info.net_name_notify ?"(y)" :"(n)" , + swnreg_info.share_name, swnreg_info.share_name_notify ?"(y)" :"(n)" ); + + tcon =cifs_swn_get_tcon (&swnreg_info); +if (!tcon) { +seq_puts (m,"(no live tcon)" ); + goto next; + } + +switch (tcon->ses->server->dstaddr.ss_family) {case AF_INET : - sa = (struct sockaddr_in *) &swnreg->tcon->ses->server->dstaddr; + sa = (struct sockaddr_in *)&tcon->ses->server->dstaddr;seq_printf (m,"%pI4" , &sa->sin_addr.s_addr);break ;case AF_INET6 : - sa6 = (struct sockaddr_in6 *) &swnreg->tcon->ses->server->dstaddr; + sa6 = (struct sockaddr_in6 *)&tcon->ses->server->dstaddr;seq_printf (m,"%pI6" , &sa6->sin6_addr.s6_addr);if (sa6->sin6_scope_id)seq_printf (m,"%%%u" , sa6->sin6_scope_id);
@@ -651,23 +842,38 @@ void cifs_swn_dump(struct seq_file *m)default :seq_puts (m,"(unknown)" ); } -seq_printf (m,"%s" , swnreg->ip_notify ?"(y)" :"(n)" ); +cifs_put_tcon (tcon, netfs_trace_tcon_ref_put_swn_notify); +next: +seq_printf (m,"%s" , swnreg_info.ip_notify ?"(y)" :"(n)" ); +cifs_swn_free_reg_info (&swnreg_info); } -mutex_unlock (&cifs_swnreg_idr_mutex); +if (ret <0 ) +seq_printf (m,"\nFailed to snapshot witness registration: %d" , ret);seq_puts (m,"\n" ); }void cifs_swn_check (void ) { -struct cifs_swn_reg *swnreg; -int id; +struct cifs_swn_reg_info swnreg_info; +struct cifs_tcon *tcon; +int id =0 ;int ret; -mutex_lock (&cifs_swnreg_idr_mutex); -idr_for_each_entry (&cifs_swnreg_idr, swnreg, id) { - ret =cifs_swn_send_register_message (swnreg); +while ((ret =cifs_swn_get_next_reg_info (&id, &swnreg_info)) >0 ) { + tcon =cifs_swn_get_tcon (&swnreg_info); +if (!tcon) { +cifs_dbg (FYI ,"%s: registration id %d has no live tcon\n" , + __func__, swnreg_info.id); + goto free_info; + } + + ret =cifs_swn_send_register_message (&swnreg_info, tcon);if (ret <0 )cifs_dbg (FYI ,"%s: Failed to send register message: %d\n" , __func__, ret); +cifs_put_tcon (tcon, netfs_trace_tcon_ref_put_swn_notify); +free_info: +cifs_swn_free_reg_info (&swnreg_info); } -mutex_unlock (&cifs_swnreg_idr_mutex); +if (ret <0 ) +cifs_dbg (FYI ,"%s: Failed to snapshot registration: %d\n" , __func__, ret); }
fs/smb/client/trace.h +2 -0
@@ -32,6 +32,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_notify, "GET SwnNot") \ 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" ) \
@@ -41,6 +42,7 @@ EM(netfs_trace_tcon_ref_put_mnt_ctx, "PUT MntCtx") \ 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_swn_notify, "PUT SwnNot") \ EM(netfs_trace_tcon_ref_put_tlink, "PUT Tlink ") \ EM(netfs_trace_tcon_ref_see_cancelled_close, "SEE Cn-Cls") \EM (netfs_trace_tcon_ref_see_fscache_collision,"SEE FV-CO!" ) \
Signature · 1 line
2.53.0