public inbox for linux-nfs@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v5 0/7] Automatic NFSv4 state revocation on filesystem unmount
@ 2026-03-26 17:55 Chuck Lever
  2026-03-26 17:55 ` [PATCH v5 1/7] NFSD: Extract revoke_one_stid() utility function Chuck Lever
                   ` (8 more replies)
  0 siblings, 9 replies; 22+ messages in thread
From: Chuck Lever @ 2026-03-26 17:55 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey
  Cc: linux-nfs, linux-fsdevel, Chuck Lever

When an NFS server exports a filesystem and clients hold NFSv4
state (opens, locks, delegations), unmounting the underlying
filesystem fails with EBUSY. The /proc/fs/nfsd/unlock_ip and
/proc/fs/nfsd/unlock_fs procfs interfaces handle this, but have
no netlink equivalents, and unlock_fs operates at whole-superblock
granularity.

This series adds three new NFSD netlink commands, each with its own
attribute set:

 - NFSD_CMD_UNLOCK_IP releases NLM locks held by a client IP
   address. Netlink equivalent of write_unlock_ip.

 - NFSD_CMD_UNLOCK_FILESYSTEM revokes all NFS state on a
   superblock. Netlink equivalent of write_unlock_fs.

 - NFSD_CMD_UNLOCK_EXPORT revokes NFSv4 state acquired through
   exports of a specific path, regardless of client.

UNLOCK_FILESYSTEM and UNLOCK_EXPORT serve different intents.
UNLOCK_FILESYSTEM means "unmounting /data, release everything
on this superblock." UNLOCK_EXPORT means "no clients remain for
/data/projectA, release only the state acquired through exports
of that path." Userspace (exportfs -u) sends UNLOCK_EXPORT after
removing the last client for a given path, enabling the underlying
filesystem to be unmounted.

The path-only design for UNLOCK_EXPORT avoids the auth_domain
naming complexity (use_ipaddr vs hostname-based domains) by not
requiring the caller to identify a specific client. Since this
mechanism is to be used to enable umount, this seemed like a
reasonable compromise.

---
Changes since v4:
- 1/9 has been queued in nfsd-testing
- Split single NFSD_CMD_UNLOCK into three separate commands
- UNLOCK_EXPORT takes path only, no client attribute to avoid
  auth_domain naming complexity with use_ipaddr

Changes since v3:
- All VFS changes replaced with new netlink "unlock" operation

Changes since v2:
- Replace fs_pin with an SRCU umount notifier chain in VFS
- Merge the pending COPY cancellation patch
- Replace xa_cmpxchg() with xa_insert()
- Use cancel_work_sync() instead of flush_workqueue()
- Remove rcu_barrier()
- Correct misleading claims in kdoc comments and commit messages

Changes since v1:
- Explain why drop_client() is being renamed
- Finish implementing revocation on umount
- Rename pin_insert_group
- Clarified log output and code comments
- Hold nfsd_mutex while closing nfsd_files

---
Chuck Lever (7):
      NFSD: Extract revoke_one_stid() utility function
      NFSD: Add NFSD_CMD_UNLOCK_IP netlink command
      NFSD: Add NFSD_CMD_UNLOCK_FILESYSTEM netlink command
      NFSD: Replace idr_for_each_entry_ul in find_one_sb_stid()
      NFSD: Track svc_export in nfs4_stid
      NFSD: Add NFSD_CMD_UNLOCK_EXPORT netlink command
      NFSD: Close cached file handles when revoking export state

 Documentation/netlink/specs/nfsd.yaml |  61 +++++++++
 fs/nfsd/filecache.c                   |  46 +++++++
 fs/nfsd/filecache.h                   |   1 +
 fs/nfsd/netlink.c                     |  36 ++++++
 fs/nfsd/netlink.h                     |   3 +
 fs/nfsd/nfs4layouts.c                 |   2 +
 fs/nfsd/nfs4state.c                   | 226 +++++++++++++++++++++++-----------
 fs/nfsd/nfsctl.c                      | 126 ++++++++++++++++++-
 fs/nfsd/state.h                       |   6 +
 fs/nfsd/trace.h                       |  32 ++++-
 include/uapi/linux/nfsd_netlink.h     |  24 ++++
 11 files changed, 484 insertions(+), 79 deletions(-)
---
base-commit: 65058e9e9b20619f920397f529072e853dd43811
change-id: 20260318-umount-kills-nfsv4-state-138218f2f4e0

Best regards,
--  
Chuck Lever


^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH v5 1/7] NFSD: Extract revoke_one_stid() utility function
  2026-03-26 17:55 [PATCH v5 0/7] Automatic NFSv4 state revocation on filesystem unmount Chuck Lever
@ 2026-03-26 17:55 ` Chuck Lever
  2026-03-27  5:26   ` kernel test robot
                     ` (5 more replies)
  2026-03-26 17:55 ` [PATCH v5 2/7] NFSD: Add NFSD_CMD_UNLOCK_IP netlink command Chuck Lever
                   ` (7 subsequent siblings)
  8 siblings, 6 replies; 22+ messages in thread
From: Chuck Lever @ 2026-03-26 17:55 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey
  Cc: linux-nfs, linux-fsdevel, Chuck Lever

From: Chuck Lever <chuck.lever@oracle.com>

The per-stateid revocation logic in nfsd4_revoke_states() handles
four stateid types in a deeply nested switch. Extract two helpers:

revoke_ol_stid() performs admin-revocation of an open or lock
stateid with st_mutex already held: marks the stateid as
SC_STATUS_ADMIN_REVOKED, closes POSIX locks for lock stateids,
and releases file access.

revoke_one_stid() dispatches by sc_type, acquires st_mutex with
the appropriate lockdep class for open and lock stateids, and
handles delegation unhash and layout close inline.

No functional change. Preparation for adding export-scoped state
revocation which reuses revoke_one_stid().

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
 fs/nfsd/nfs4state.c | 137 ++++++++++++++++++++++++++--------------------------
 1 file changed, 68 insertions(+), 69 deletions(-)

diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index 07df4511ba23..eb1bd1aae8f5 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -1775,6 +1775,73 @@ static struct nfs4_stid *find_one_sb_stid(struct nfs4_client *clp,
 	return stid;
 }
 
+static void revoke_ol_stid(struct nfs4_client *clp,
+			   struct nfs4_ol_stateid *stp)
+{
+	struct nfs4_stid *stid = &stp->st_stid;
+
+	lockdep_assert_held(&stp->st_mutex);
+	spin_lock(&clp->cl_lock);
+	if (stid->sc_status == 0) {
+		stid->sc_status |= SC_STATUS_ADMIN_REVOKED;
+		atomic_inc(&clp->cl_admin_revoked);
+		spin_unlock(&clp->cl_lock);
+		if (stid->sc_type == SC_TYPE_LOCK) {
+			struct nfs4_lockowner *lo =
+				lockowner(stp->st_stateowner);
+			struct nfsd_file *nf;
+
+			nf = find_any_file(stp->st_stid.sc_file);
+			if (nf) {
+				get_file(nf->nf_file);
+				filp_close(nf->nf_file, (fl_owner_t)lo);
+				nfsd_file_put(nf);
+			}
+		}
+		release_all_access(stp);
+	} else
+		spin_unlock(&clp->cl_lock);
+}
+
+static void revoke_one_stid(struct nfs4_client *clp, struct nfs4_stid *stid)
+{
+	struct nfs4_ol_stateid *stp;
+	struct nfs4_delegation *dp;
+
+	switch (stid->sc_type) {
+	case SC_TYPE_OPEN:
+		stp = openlockstateid(stid);
+		mutex_lock_nested(&stp->st_mutex, OPEN_STATEID_MUTEX);
+		revoke_ol_stid(clp, stp);
+		mutex_unlock(&stp->st_mutex);
+		break;
+	case SC_TYPE_LOCK:
+		stp = openlockstateid(stid);
+		mutex_lock_nested(&stp->st_mutex, LOCK_STATEID_MUTEX);
+		revoke_ol_stid(clp, stp);
+		mutex_unlock(&stp->st_mutex);
+		break;
+	case SC_TYPE_DELEG:
+		/*
+		 * Extra reference guards against concurrent FREE_STATEID.
+		 */
+		refcount_inc(&stid->sc_count);
+		dp = delegstateid(stid);
+		spin_lock(&state_lock);
+		if (!unhash_delegation_locked(dp, SC_STATUS_ADMIN_REVOKED))
+			dp = NULL;
+		spin_unlock(&state_lock);
+		if (dp)
+			revoke_delegation(dp);
+		else
+			nfs4_put_stid(stid);
+		break;
+	case SC_TYPE_LAYOUT:
+		nfsd4_close_layout(layoutstateid(stid));
+		break;
+	}
+}
+
 /**
  * nfsd4_revoke_states - revoke all nfsv4 states associated with given filesystem
  * @nn:   used to identify instance of nfsd (there is one per net namespace)
@@ -1805,76 +1872,8 @@ void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb)
 			struct nfs4_stid *stid = find_one_sb_stid(clp, sb,
 								  sc_types);
 			if (stid) {
-				struct nfs4_ol_stateid *stp;
-				struct nfs4_delegation *dp;
-				struct nfs4_layout_stateid *ls;
-
 				spin_unlock(&nn->client_lock);
-				switch (stid->sc_type) {
-				case SC_TYPE_OPEN:
-					stp = openlockstateid(stid);
-					mutex_lock_nested(&stp->st_mutex,
-							  OPEN_STATEID_MUTEX);
-
-					spin_lock(&clp->cl_lock);
-					if (stid->sc_status == 0) {
-						stid->sc_status |=
-							SC_STATUS_ADMIN_REVOKED;
-						atomic_inc(&clp->cl_admin_revoked);
-						spin_unlock(&clp->cl_lock);
-						release_all_access(stp);
-					} else
-						spin_unlock(&clp->cl_lock);
-					mutex_unlock(&stp->st_mutex);
-					break;
-				case SC_TYPE_LOCK:
-					stp = openlockstateid(stid);
-					mutex_lock_nested(&stp->st_mutex,
-							  LOCK_STATEID_MUTEX);
-					spin_lock(&clp->cl_lock);
-					if (stid->sc_status == 0) {
-						struct nfs4_lockowner *lo =
-							lockowner(stp->st_stateowner);
-						struct nfsd_file *nf;
-
-						stid->sc_status |=
-							SC_STATUS_ADMIN_REVOKED;
-						atomic_inc(&clp->cl_admin_revoked);
-						spin_unlock(&clp->cl_lock);
-						nf = find_any_file(stp->st_stid.sc_file);
-						if (nf) {
-							get_file(nf->nf_file);
-							filp_close(nf->nf_file,
-								   (fl_owner_t)lo);
-							nfsd_file_put(nf);
-						}
-						release_all_access(stp);
-					} else
-						spin_unlock(&clp->cl_lock);
-					mutex_unlock(&stp->st_mutex);
-					break;
-				case SC_TYPE_DELEG:
-					/* Extra reference guards against concurrent
-					 * FREE_STATEID; revoke_delegation() consumes
-					 * it, otherwise release it directly.
-					 */
-					refcount_inc(&stid->sc_count);
-					dp = delegstateid(stid);
-					spin_lock(&nn->deleg_lock);
-					if (!unhash_delegation_locked(
-						    dp, SC_STATUS_ADMIN_REVOKED))
-						dp = NULL;
-					spin_unlock(&nn->deleg_lock);
-					if (dp)
-						revoke_delegation(dp);
-					else
-						nfs4_put_stid(stid);
-					break;
-				case SC_TYPE_LAYOUT:
-					ls = layoutstateid(stid);
-					nfsd4_close_layout(ls);
-					break;
-				}
+				revoke_one_stid(clp, stid);
 				nfs4_put_stid(stid);
 				spin_lock(&nn->client_lock);
 				if (clp->cl_minorversion == 0)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v5 2/7] NFSD: Add NFSD_CMD_UNLOCK_IP netlink command
  2026-03-26 17:55 [PATCH v5 0/7] Automatic NFSv4 state revocation on filesystem unmount Chuck Lever
  2026-03-26 17:55 ` [PATCH v5 1/7] NFSD: Extract revoke_one_stid() utility function Chuck Lever
@ 2026-03-26 17:55 ` Chuck Lever
  2026-03-27 12:06   ` Jeff Layton
  2026-03-26 17:55 ` [PATCH v5 3/7] NFSD: Add NFSD_CMD_UNLOCK_FILESYSTEM " Chuck Lever
                   ` (6 subsequent siblings)
  8 siblings, 1 reply; 22+ messages in thread
From: Chuck Lever @ 2026-03-26 17:55 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey
  Cc: linux-nfs, linux-fsdevel, Chuck Lever

From: Chuck Lever <chuck.lever@oracle.com>

The existing write_unlock_ip procfs interface releases NLM file
locks held by a specific client IP address, but procfs provides
no structured way to extend that operation to other scopes such as
revoking NFSv4 state.

Add NFSD_CMD_UNLOCK_IP as a dedicated netlink command for
releasing NLM locks by client address. The command accepts a
binary sockaddr_in or sockaddr_in6 in its address attribute.
The handler validates the address family and length, then calls
nlmsvc_unlock_all_by_ip() to release matching NLM locks.  Because
lockd is a single global instance, that call operates across
all network namespaces regardless of which namespace the caller
inhabits.

A separate netlink command for filesystem-scoped unlock is added in
a subsequent commit.

The nfsd_ctl_unlock_ip tracepoint is updated from string-based
address logging to __sockaddr, which stores the binary sockaddr
and formats it with %pISpc. This affects both the new netlink path
and the existing procfs write_unlock_ip path, giving consistent
structured output in both cases.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
 Documentation/netlink/specs/nfsd.yaml | 18 ++++++++++++++++
 fs/nfsd/netlink.c                     | 12 +++++++++++
 fs/nfsd/netlink.h                     |  1 +
 fs/nfsd/nfsctl.c                      | 40 ++++++++++++++++++++++++++++++++++-
 fs/nfsd/trace.h                       | 13 ++++++------
 include/uapi/linux/nfsd_netlink.h     |  8 +++++++
 6 files changed, 85 insertions(+), 7 deletions(-)

diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml
index 8ab43c8253b2..9918b9a84d88 100644
--- a/Documentation/netlink/specs/nfsd.yaml
+++ b/Documentation/netlink/specs/nfsd.yaml
@@ -132,6 +132,15 @@ attribute-sets:
       -
         name: npools
         type: u32
+  -
+    name: unlock-ip
+    attributes:
+      -
+        name: address
+        type: binary
+        doc: struct sockaddr_in or struct sockaddr_in6.
+        checks:
+          min-len: 16
 
 operations:
   list:
@@ -233,3 +242,12 @@ operations:
           attributes:
             - mode
             - npools
+    -
+      name: unlock-ip
+      doc: release NLM locks held by an IP address
+      attribute-set: unlock-ip
+      flags: [admin-perm]
+      do:
+        request:
+          attributes:
+            - address
diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c
index 81c943345d13..6b7221ce6869 100644
--- a/fs/nfsd/netlink.c
+++ b/fs/nfsd/netlink.c
@@ -48,6 +48,11 @@ static const struct nla_policy nfsd_pool_mode_set_nl_policy[NFSD_A_POOL_MODE_MOD
 	[NFSD_A_POOL_MODE_MODE] = { .type = NLA_NUL_STRING, },
 };
 
+/* NFSD_CMD_UNLOCK_IP - do */
+static const struct nla_policy nfsd_unlock_ip_nl_policy[NFSD_A_UNLOCK_IP_ADDRESS + 1] = {
+	[NFSD_A_UNLOCK_IP_ADDRESS] = NLA_POLICY_MIN_LEN(16),
+};
+
 /* Ops table for nfsd */
 static const struct genl_split_ops nfsd_nl_ops[] = {
 	{
@@ -103,6 +108,13 @@ static const struct genl_split_ops nfsd_nl_ops[] = {
 		.doit	= nfsd_nl_pool_mode_get_doit,
 		.flags	= GENL_CMD_CAP_DO,
 	},
+	{
+		.cmd		= NFSD_CMD_UNLOCK_IP,
+		.doit		= nfsd_nl_unlock_ip_doit,
+		.policy		= nfsd_unlock_ip_nl_policy,
+		.maxattr	= NFSD_A_UNLOCK_IP_ADDRESS,
+		.flags		= GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+	},
 };
 
 struct genl_family nfsd_nl_family __ro_after_init = {
diff --git a/fs/nfsd/netlink.h b/fs/nfsd/netlink.h
index 478117ff6b8c..3c2d5996612f 100644
--- a/fs/nfsd/netlink.h
+++ b/fs/nfsd/netlink.h
@@ -26,6 +26,7 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info);
 int nfsd_nl_listener_get_doit(struct sk_buff *skb, struct genl_info *info);
 int nfsd_nl_pool_mode_set_doit(struct sk_buff *skb, struct genl_info *info);
 int nfsd_nl_pool_mode_get_doit(struct sk_buff *skb, struct genl_info *info);
+int nfsd_nl_unlock_ip_doit(struct sk_buff *skb, struct genl_info *info);
 
 extern struct genl_family nfsd_nl_family;
 
diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index 988a79ec4a79..e1e89d52e6de 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -246,7 +246,7 @@ static ssize_t write_unlock_ip(struct file *file, char *buf, size_t size)
 	if (rpc_pton(net, fo_path, size, sap, salen) == 0)
 		return -EINVAL;
 
-	trace_nfsd_ctl_unlock_ip(net, buf);
+	trace_nfsd_ctl_unlock_ip(net, sap, svc_addr_len(sap));
 	return nlmsvc_unlock_all_by_ip(sap);
 }
 
@@ -2200,6 +2200,44 @@ int nfsd_nl_pool_mode_get_doit(struct sk_buff *skb, struct genl_info *info)
 	return err;
 }
 
+/**
+ * nfsd_nl_unlock_ip_doit - release NLM locks held by an IP address
+ * @skb: reply buffer
+ * @info: netlink metadata and command arguments
+ *
+ * Return: 0 on success or a negative errno.
+ */
+int nfsd_nl_unlock_ip_doit(struct sk_buff *skb, struct genl_info *info)
+{
+	struct sockaddr *sap;
+
+	if (GENL_REQ_ATTR_CHECK(info, NFSD_A_UNLOCK_IP_ADDRESS))
+		return -EINVAL;
+	sap = nla_data(info->attrs[NFSD_A_UNLOCK_IP_ADDRESS]);
+	switch (sap->sa_family) {
+	case AF_INET:
+		if (nla_len(info->attrs[NFSD_A_UNLOCK_IP_ADDRESS]) <
+		    sizeof(struct sockaddr_in))
+			return -EINVAL;
+		break;
+	case AF_INET6:
+		if (nla_len(info->attrs[NFSD_A_UNLOCK_IP_ADDRESS]) <
+		    sizeof(struct sockaddr_in6))
+			return -EINVAL;
+		break;
+	default:
+		return -EAFNOSUPPORT;
+	}
+	/*
+	 * nlmsvc_unlock_all_by_ip() releases matching locks
+	 * across all network namespaces because lockd operates
+	 * a single global instance.
+	 */
+	trace_nfsd_ctl_unlock_ip(genl_info_net(info), sap,
+				 svc_addr_len(sap));
+	return nlmsvc_unlock_all_by_ip(sap);
+}
+
 /**
  * nfsd_net_init - Prepare the nfsd_net portion of a new net namespace
  * @net: a freshly-created network namespace
diff --git a/fs/nfsd/trace.h b/fs/nfsd/trace.h
index 5ad38f50836d..976815f6f30f 100644
--- a/fs/nfsd/trace.h
+++ b/fs/nfsd/trace.h
@@ -1985,19 +1985,20 @@ TRACE_EVENT(nfsd_cb_recall_any_done,
 TRACE_EVENT(nfsd_ctl_unlock_ip,
 	TP_PROTO(
 		const struct net *net,
-		const char *address
+		const struct sockaddr *addr,
+		const unsigned int addrlen
 	),
-	TP_ARGS(net, address),
+	TP_ARGS(net, addr, addrlen),
 	TP_STRUCT__entry(
 		__field(unsigned int, netns_ino)
-		__string(address, address)
+		__sockaddr(addr, addrlen)
 	),
 	TP_fast_assign(
 		__entry->netns_ino = net->ns.inum;
-		__assign_str(address);
+		__assign_sockaddr(addr, addr, addrlen);
 	),
-	TP_printk("address=%s",
-		__get_str(address)
+	TP_printk("addr=%pISpc",
+		__get_sockaddr(addr)
 	)
 );
 
diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h
index 97c7447f4d14..4153e9c69fbf 100644
--- a/include/uapi/linux/nfsd_netlink.h
+++ b/include/uapi/linux/nfsd_netlink.h
@@ -81,6 +81,13 @@ enum {
 	NFSD_A_POOL_MODE_MAX = (__NFSD_A_POOL_MODE_MAX - 1)
 };
 
+enum {
+	NFSD_A_UNLOCK_IP_ADDRESS = 1,
+
+	__NFSD_A_UNLOCK_IP_MAX,
+	NFSD_A_UNLOCK_IP_MAX = (__NFSD_A_UNLOCK_IP_MAX - 1)
+};
+
 enum {
 	NFSD_CMD_RPC_STATUS_GET = 1,
 	NFSD_CMD_THREADS_SET,
@@ -91,6 +98,7 @@ enum {
 	NFSD_CMD_LISTENER_GET,
 	NFSD_CMD_POOL_MODE_SET,
 	NFSD_CMD_POOL_MODE_GET,
+	NFSD_CMD_UNLOCK_IP,
 
 	__NFSD_CMD_MAX,
 	NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v5 3/7] NFSD: Add NFSD_CMD_UNLOCK_FILESYSTEM netlink command
  2026-03-26 17:55 [PATCH v5 0/7] Automatic NFSv4 state revocation on filesystem unmount Chuck Lever
  2026-03-26 17:55 ` [PATCH v5 1/7] NFSD: Extract revoke_one_stid() utility function Chuck Lever
  2026-03-26 17:55 ` [PATCH v5 2/7] NFSD: Add NFSD_CMD_UNLOCK_IP netlink command Chuck Lever
@ 2026-03-26 17:55 ` Chuck Lever
  2026-03-26 17:55 ` [PATCH v5 4/7] NFSD: Replace idr_for_each_entry_ul in find_one_sb_stid() Chuck Lever
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 22+ messages in thread
From: Chuck Lever @ 2026-03-26 17:55 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey
  Cc: linux-nfs, linux-fsdevel, Chuck Lever

From: Chuck Lever <chuck.lever@oracle.com>

Add NFSD_CMD_UNLOCK_FILESYSTEM as a dedicated netlink command for
revoking NFS state under a filesystem path, providing a netlink
equivalent of /proc/fs/nfsd/unlock_fs.

The command requires a "path" string attribute containing the
filesystem path whose state should be released. The handler
resolves the path to its superblock, then cancels async copies,
releases NLM locks, and revokes NFSv4 state on that superblock.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
 Documentation/netlink/specs/nfsd.yaml | 16 ++++++++++++++
 fs/nfsd/netlink.c                     | 12 +++++++++++
 fs/nfsd/netlink.h                     |  1 +
 fs/nfsd/nfsctl.c                      | 40 +++++++++++++++++++++++++++++++++++
 include/uapi/linux/nfsd_netlink.h     |  8 +++++++
 5 files changed, 77 insertions(+)

diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml
index 9918b9a84d88..e83209a4a19d 100644
--- a/Documentation/netlink/specs/nfsd.yaml
+++ b/Documentation/netlink/specs/nfsd.yaml
@@ -141,6 +141,13 @@ attribute-sets:
         doc: struct sockaddr_in or struct sockaddr_in6.
         checks:
           min-len: 16
+  -
+    name: unlock-filesystem
+    attributes:
+      -
+        name: path
+        type: string
+        doc: Filesystem path whose state should be released.
 
 operations:
   list:
@@ -251,3 +258,12 @@ operations:
         request:
           attributes:
             - address
+    -
+      name: unlock-filesystem
+      doc: revoke NFS state under a filesystem path
+      attribute-set: unlock-filesystem
+      flags: [admin-perm]
+      do:
+        request:
+          attributes:
+            - path
diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c
index 6b7221ce6869..18afebaf4fef 100644
--- a/fs/nfsd/netlink.c
+++ b/fs/nfsd/netlink.c
@@ -53,6 +53,11 @@ static const struct nla_policy nfsd_unlock_ip_nl_policy[NFSD_A_UNLOCK_IP_ADDRESS
 	[NFSD_A_UNLOCK_IP_ADDRESS] = NLA_POLICY_MIN_LEN(16),
 };
 
+/* NFSD_CMD_UNLOCK_FILESYSTEM - do */
+static const struct nla_policy nfsd_unlock_filesystem_nl_policy[NFSD_A_UNLOCK_FILESYSTEM_PATH + 1] = {
+	[NFSD_A_UNLOCK_FILESYSTEM_PATH] = { .type = NLA_NUL_STRING, },
+};
+
 /* Ops table for nfsd */
 static const struct genl_split_ops nfsd_nl_ops[] = {
 	{
@@ -115,6 +120,13 @@ static const struct genl_split_ops nfsd_nl_ops[] = {
 		.maxattr	= NFSD_A_UNLOCK_IP_ADDRESS,
 		.flags		= GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
 	},
+	{
+		.cmd		= NFSD_CMD_UNLOCK_FILESYSTEM,
+		.doit		= nfsd_nl_unlock_filesystem_doit,
+		.policy		= nfsd_unlock_filesystem_nl_policy,
+		.maxattr	= NFSD_A_UNLOCK_FILESYSTEM_PATH,
+		.flags		= GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+	},
 };
 
 struct genl_family nfsd_nl_family __ro_after_init = {
diff --git a/fs/nfsd/netlink.h b/fs/nfsd/netlink.h
index 3c2d5996612f..9535782b529a 100644
--- a/fs/nfsd/netlink.h
+++ b/fs/nfsd/netlink.h
@@ -27,6 +27,7 @@ int nfsd_nl_listener_get_doit(struct sk_buff *skb, struct genl_info *info);
 int nfsd_nl_pool_mode_set_doit(struct sk_buff *skb, struct genl_info *info);
 int nfsd_nl_pool_mode_get_doit(struct sk_buff *skb, struct genl_info *info);
 int nfsd_nl_unlock_ip_doit(struct sk_buff *skb, struct genl_info *info);
+int nfsd_nl_unlock_filesystem_doit(struct sk_buff *skb, struct genl_info *info);
 
 extern struct genl_family nfsd_nl_family;
 
diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index e1e89d52e6de..ed0d12f77405 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -2238,6 +2238,46 @@ int nfsd_nl_unlock_ip_doit(struct sk_buff *skb, struct genl_info *info)
 	return nlmsvc_unlock_all_by_ip(sap);
 }
 
+/**
+ * nfsd_nl_unlock_filesystem_doit - revoke NFS state under a filesystem path
+ * @skb: reply buffer
+ * @info: netlink metadata and command arguments
+ *
+ * Return: 0 on success or a negative errno.
+ */
+int nfsd_nl_unlock_filesystem_doit(struct sk_buff *skb,
+				   struct genl_info *info)
+{
+	struct net *net = genl_info_net(info);
+	struct nfsd_net *nn = net_generic(net, nfsd_net_id);
+	struct path path;
+	int error;
+
+	if (GENL_REQ_ATTR_CHECK(info, NFSD_A_UNLOCK_FILESYSTEM_PATH))
+		return -EINVAL;
+
+	trace_nfsd_ctl_unlock_fs(net,
+			nla_data(info->attrs[NFSD_A_UNLOCK_FILESYSTEM_PATH]));
+	error = kern_path(
+			nla_data(info->attrs[NFSD_A_UNLOCK_FILESYSTEM_PATH]),
+			0, &path);
+	if (error)
+		return error;
+
+	nfsd4_cancel_copy_by_sb(net, path.dentry->d_sb);
+	error = nlmsvc_unlock_all_by_sb(path.dentry->d_sb);
+
+	mutex_lock(&nfsd_mutex);
+	if (nn->nfsd_serv)
+		nfsd4_revoke_states(nn, path.dentry->d_sb);
+	else
+		error = -EINVAL;
+	mutex_unlock(&nfsd_mutex);
+
+	path_put(&path);
+	return error;
+}
+
 /**
  * nfsd_net_init - Prepare the nfsd_net portion of a new net namespace
  * @net: a freshly-created network namespace
diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h
index 4153e9c69fbf..51f926c0b15b 100644
--- a/include/uapi/linux/nfsd_netlink.h
+++ b/include/uapi/linux/nfsd_netlink.h
@@ -88,6 +88,13 @@ enum {
 	NFSD_A_UNLOCK_IP_MAX = (__NFSD_A_UNLOCK_IP_MAX - 1)
 };
 
+enum {
+	NFSD_A_UNLOCK_FILESYSTEM_PATH = 1,
+
+	__NFSD_A_UNLOCK_FILESYSTEM_MAX,
+	NFSD_A_UNLOCK_FILESYSTEM_MAX = (__NFSD_A_UNLOCK_FILESYSTEM_MAX - 1)
+};
+
 enum {
 	NFSD_CMD_RPC_STATUS_GET = 1,
 	NFSD_CMD_THREADS_SET,
@@ -99,6 +106,7 @@ enum {
 	NFSD_CMD_POOL_MODE_SET,
 	NFSD_CMD_POOL_MODE_GET,
 	NFSD_CMD_UNLOCK_IP,
+	NFSD_CMD_UNLOCK_FILESYSTEM,
 
 	__NFSD_CMD_MAX,
 	NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v5 4/7] NFSD: Replace idr_for_each_entry_ul in find_one_sb_stid()
  2026-03-26 17:55 [PATCH v5 0/7] Automatic NFSv4 state revocation on filesystem unmount Chuck Lever
                   ` (2 preceding siblings ...)
  2026-03-26 17:55 ` [PATCH v5 3/7] NFSD: Add NFSD_CMD_UNLOCK_FILESYSTEM " Chuck Lever
@ 2026-03-26 17:55 ` Chuck Lever
  2026-03-26 17:55 ` [PATCH v5 5/7] NFSD: Track svc_export in nfs4_stid Chuck Lever
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 22+ messages in thread
From: Chuck Lever @ 2026-03-26 17:55 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey
  Cc: linux-nfs, linux-fsdevel, Chuck Lever

From: Chuck Lever <chuck.lever@oracle.com>

Replace idr_for_each_entry_ul() with a while loop over
idr_get_next_ul() for consistency with find_one_export_stid(),
added in a subsequent commit.

No change in behavior.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
 fs/nfsd/nfs4state.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index eb1bd1aae8f5..62ebc7243c4f 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -1760,17 +1760,19 @@ static struct nfs4_stid *find_one_sb_stid(struct nfs4_client *clp,
 					  struct super_block *sb,
 					  unsigned int sc_types)
 {
-	unsigned long id, tmp;
+	unsigned long id = 0;
 	struct nfs4_stid *stid;
 
 	spin_lock(&clp->cl_lock);
-	idr_for_each_entry_ul(&clp->cl_stateids, stid, tmp, id)
+	while ((stid = idr_get_next_ul(&clp->cl_stateids, &id)) != NULL) {
 		if ((stid->sc_type & sc_types) &&
 		    stid->sc_status == 0 &&
 		    stid->sc_file->fi_inode->i_sb == sb) {
 			refcount_inc(&stid->sc_count);
 			break;
 		}
+		id++;
+	}
 	spin_unlock(&clp->cl_lock);
 	return stid;
 }

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v5 5/7] NFSD: Track svc_export in nfs4_stid
  2026-03-26 17:55 [PATCH v5 0/7] Automatic NFSv4 state revocation on filesystem unmount Chuck Lever
                   ` (3 preceding siblings ...)
  2026-03-26 17:55 ` [PATCH v5 4/7] NFSD: Replace idr_for_each_entry_ul in find_one_sb_stid() Chuck Lever
@ 2026-03-26 17:55 ` Chuck Lever
  2026-03-26 17:55 ` [PATCH v5 6/7] NFSD: Add NFSD_CMD_UNLOCK_EXPORT netlink command Chuck Lever
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 22+ messages in thread
From: Chuck Lever @ 2026-03-26 17:55 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey
  Cc: linux-nfs, linux-fsdevel, Chuck Lever

From: Chuck Lever <chuck.lever@oracle.com>

Add an sc_export field to struct nfs4_stid so that each stateid
records the export under which it was acquired.  The export
reference is taken via exp_get() at stateid creation and released
via exp_put() in nfs4_put_stid().

Open stateids record the export from current_fh->fh_export.
Lock stateids and delegations inherit the export from their
parent open stateid. Layout stateids inherit from their
parent stateid. Directory delegations record the export from
cstate->current_fh.

A subsequent commit uses sc_export to scope state revocation to a
specific export, avoiding the need to walk inode dentry aliases at
revocation time.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
 fs/nfsd/nfs4layouts.c |  2 ++
 fs/nfsd/nfs4state.c   | 16 +++++++++++++++-
 fs/nfsd/state.h       |  1 +
 3 files changed, 18 insertions(+), 1 deletion(-)

diff --git a/fs/nfsd/nfs4layouts.c b/fs/nfsd/nfs4layouts.c
index 69e41105efdd..83d8fda53efd 100644
--- a/fs/nfsd/nfs4layouts.c
+++ b/fs/nfsd/nfs4layouts.c
@@ -247,6 +247,8 @@ nfsd4_alloc_layout_stateid(struct nfsd4_compound_state *cstate,
 
 	get_nfs4_file(fp);
 	stp->sc_file = fp;
+	if (parent->sc_export)
+		stp->sc_export = exp_get(parent->sc_export);
 
 	ls = layoutstateid(stp);
 	INIT_LIST_HEAD(&ls->ls_perclnt);
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index 62ebc7243c4f..ebbc83d7877e 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -1168,6 +1168,7 @@ alloc_init_deleg(struct nfs4_client *clp, struct nfs4_file *fp,
 void
 nfs4_put_stid(struct nfs4_stid *s)
 {
+	struct svc_export *exp = s->sc_export;
 	struct nfs4_file *fp = s->sc_file;
 	struct nfs4_client *clp = s->sc_client;
 
@@ -1183,6 +1184,8 @@ nfs4_put_stid(struct nfs4_stid *s)
 	nfs4_free_cpntf_statelist(clp->net, s);
 	spin_unlock(&clp->cl_lock);
 	s->sc_free(s);
+	if (exp)
+		exp_put(exp);
 	if (fp)
 		put_nfs4_file(fp);
 }
@@ -6172,6 +6175,8 @@ nfs4_set_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
 	dp = alloc_init_deleg(clp, fp, odstate, dl_type);
 	if (!dp)
 		goto out_delegees;
+	if (stp->st_stid.sc_export)
+		dp->dl_stid.sc_export = exp_get(stp->st_stid.sc_export);
 
 	fl = nfs4_alloc_init_lease(dp);
 	if (!fl)
@@ -6506,8 +6511,11 @@ nfsd4_process_open2(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nf
 			goto out;
 		}
 
-		if (!open->op_stp)
+		if (!open->op_stp) {
 			new_stp = true;
+			stp->st_stid.sc_export =
+				exp_get(current_fh->fh_export);
+		}
 	}
 
 	/*
@@ -8203,6 +8211,9 @@ init_lock_stateid(struct nfs4_ol_stateid *stp, struct nfs4_lockowner *lo,
 	stp->st_stateowner = nfs4_get_stateowner(&lo->lo_owner);
 	get_nfs4_file(fp);
 	stp->st_stid.sc_file = fp;
+	if (open_stp->st_stid.sc_export)
+		stp->st_stid.sc_export =
+			exp_get(open_stp->st_stid.sc_export);
 	stp->st_access_bmap = 0;
 	stp->st_deny_bmap = open_stp->st_deny_bmap;
 	stp->st_openstp = open_stp;
@@ -9529,6 +9540,9 @@ nfsd_get_dir_deleg(struct nfsd4_compound_state *cstate,
 	dp = alloc_init_deleg(clp, fp, NULL, NFS4_OPEN_DELEGATE_READ);
 	if (!dp)
 		goto out_delegees;
+	if (cstate->current_fh.fh_export)
+		dp->dl_stid.sc_export =
+			exp_get(cstate->current_fh.fh_export);
 
 	fl = nfs4_alloc_init_lease(dp);
 	if (!fl)
diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h
index 953675eba5c3..7d7e99eeffa5 100644
--- a/fs/nfsd/state.h
+++ b/fs/nfsd/state.h
@@ -145,6 +145,7 @@ struct nfs4_stid {
 	spinlock_t		sc_lock;
 	struct nfs4_client	*sc_client;
 	struct nfs4_file	*sc_file;
+	struct svc_export	*sc_export;
 	void			(*sc_free)(struct nfs4_stid *);
 };
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v5 6/7] NFSD: Add NFSD_CMD_UNLOCK_EXPORT netlink command
  2026-03-26 17:55 [PATCH v5 0/7] Automatic NFSv4 state revocation on filesystem unmount Chuck Lever
                   ` (4 preceding siblings ...)
  2026-03-26 17:55 ` [PATCH v5 5/7] NFSD: Track svc_export in nfs4_stid Chuck Lever
@ 2026-03-26 17:55 ` Chuck Lever
  2026-03-26 17:55 ` [PATCH v5 7/7] NFSD: Close cached file handles when revoking export state Chuck Lever
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 22+ messages in thread
From: Chuck Lever @ 2026-03-26 17:55 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey
  Cc: linux-nfs, linux-fsdevel, Chuck Lever

From: Chuck Lever <chuck.lever@oracle.com>

When a filesystem is exported to NFS clients, NFSv4 state
(opens, locks, delegations, layouts) holds references that
prevent the underlying filesystem from being unmounted.
NFSD_CMD_UNLOCK_FILESYSTEM addresses this at superblock
granularity, but administrators unexporting a single path on a
shared filesystem (e.g., one of several exports on the same device)
need finer control.

Add NFSD_CMD_UNLOCK_EXPORT, which revokes NFSv4 state acquired
through exports of a specific path.  Matching is by path identity
(dentry + vfsmount) via the sc_export field on each nfs4_stid,
so multiple svc_export objects for the same path -- one per
auth_domain -- are handled correctly without requiring the caller
to name a specific client.

The command takes a single "path" attribute.  Userspace (exportfs
-u) sends this after removing the last client for a given path,
enabling the underlying filesystem to be unmounted.  When multiple
clients share an export path, individual unexports do not trigger
state revocation; only the final one does.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
 Documentation/netlink/specs/nfsd.yaml | 27 ++++++++++++++
 fs/nfsd/netlink.c                     | 12 +++++++
 fs/nfsd/netlink.h                     |  1 +
 fs/nfsd/nfs4state.c                   | 67 +++++++++++++++++++++++++++++++++++
 fs/nfsd/nfsctl.c                      | 45 +++++++++++++++++++++++
 fs/nfsd/state.h                       |  5 +++
 fs/nfsd/trace.h                       | 19 ++++++++++
 include/uapi/linux/nfsd_netlink.h     |  8 +++++
 8 files changed, 184 insertions(+)

diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml
index e83209a4a19d..2607c4bd8920 100644
--- a/Documentation/netlink/specs/nfsd.yaml
+++ b/Documentation/netlink/specs/nfsd.yaml
@@ -148,6 +148,19 @@ attribute-sets:
         name: path
         type: string
         doc: Filesystem path whose state should be released.
+  -
+    name: unlock-export
+    attributes:
+      -
+        name: path
+        type: string
+        doc: >-
+          Export path whose NFSv4 state should be revoked.
+          All state (opens, locks, delegations, layouts) acquired
+          through any export of this path is revoked, regardless
+          of which client holds the state. Intended for use after
+          all clients have been unexported from a given path,
+          enabling the underlying filesystem to be unmounted.
 
 operations:
   list:
@@ -267,3 +280,17 @@ operations:
         request:
           attributes:
             - path
+    -
+      name: unlock-export
+      doc: >-
+        Revoke NFSv4 state acquired through exports of a given path.
+        Unlike unlock-filesystem, which operates at superblock granularity,
+        this command targets only state associated with a specific export
+        path. Userspace (exportfs -u) sends this after removing the last
+        client for a path so the underlying filesystem can be unmounted.
+      attribute-set: unlock-export
+      flags: [admin-perm]
+      do:
+        request:
+          attributes:
+            - path
diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c
index 18afebaf4fef..3b885084b9f8 100644
--- a/fs/nfsd/netlink.c
+++ b/fs/nfsd/netlink.c
@@ -58,6 +58,11 @@ static const struct nla_policy nfsd_unlock_filesystem_nl_policy[NFSD_A_UNLOCK_FI
 	[NFSD_A_UNLOCK_FILESYSTEM_PATH] = { .type = NLA_NUL_STRING, },
 };
 
+/* NFSD_CMD_UNLOCK_EXPORT - do */
+static const struct nla_policy nfsd_unlock_export_nl_policy[NFSD_A_UNLOCK_EXPORT_PATH + 1] = {
+	[NFSD_A_UNLOCK_EXPORT_PATH] = { .type = NLA_NUL_STRING, },
+};
+
 /* Ops table for nfsd */
 static const struct genl_split_ops nfsd_nl_ops[] = {
 	{
@@ -127,6 +132,13 @@ static const struct genl_split_ops nfsd_nl_ops[] = {
 		.maxattr	= NFSD_A_UNLOCK_FILESYSTEM_PATH,
 		.flags		= GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
 	},
+	{
+		.cmd		= NFSD_CMD_UNLOCK_EXPORT,
+		.doit		= nfsd_nl_unlock_export_doit,
+		.policy		= nfsd_unlock_export_nl_policy,
+		.maxattr	= NFSD_A_UNLOCK_EXPORT_PATH,
+		.flags		= GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+	},
 };
 
 struct genl_family nfsd_nl_family __ro_after_init = {
diff --git a/fs/nfsd/netlink.h b/fs/nfsd/netlink.h
index 9535782b529a..5b946f23059a 100644
--- a/fs/nfsd/netlink.h
+++ b/fs/nfsd/netlink.h
@@ -28,6 +28,7 @@ int nfsd_nl_pool_mode_set_doit(struct sk_buff *skb, struct genl_info *info);
 int nfsd_nl_pool_mode_get_doit(struct sk_buff *skb, struct genl_info *info);
 int nfsd_nl_unlock_ip_doit(struct sk_buff *skb, struct genl_info *info);
 int nfsd_nl_unlock_filesystem_doit(struct sk_buff *skb, struct genl_info *info);
+int nfsd_nl_unlock_export_doit(struct sk_buff *skb, struct genl_info *info);
 
 extern struct genl_family nfsd_nl_family;
 
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index ebbc83d7877e..eca49020f4cf 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -1896,6 +1896,73 @@ void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb)
 	spin_unlock(&nn->client_lock);
 }
 
+static struct nfs4_stid *find_one_export_stid(struct nfs4_client *clp,
+					     const struct path *path,
+					     unsigned int sc_types)
+{
+	unsigned long id = 0;
+	struct nfs4_stid *stid;
+
+	spin_lock(&clp->cl_lock);
+	while ((stid = idr_get_next_ul(&clp->cl_stateids, &id)) != NULL) {
+		if ((stid->sc_type & sc_types) &&
+		    stid->sc_status == 0 &&
+		    stid->sc_export &&
+		    path_equal(&stid->sc_export->ex_path, path)) {
+			refcount_inc(&stid->sc_count);
+			break;
+		}
+		id++;
+	}
+	spin_unlock(&clp->cl_lock);
+	return stid;
+}
+
+/**
+ * nfsd4_revoke_export_states - revoke nfsv4 states acquired through an export
+ * @nn:   used to identify instance of nfsd (there is one per net namespace)
+ * @path: export path whose states should be revoked
+ *
+ * All nfs4 states (open, lock, delegation, layout) acquired through any
+ * export matching @path are revoked, regardless of which client holds
+ * them.  Matching is by path identity (dentry + vfsmount), so multiple
+ * svc_export objects for the same path -- one per auth_domain -- are
+ * handled correctly.
+ *
+ * Userspace (exportfs -u) sends this after removing the last client
+ * for a path, enabling the underlying filesystem to be unmounted.
+ */
+void nfsd4_revoke_export_states(struct nfsd_net *nn, const struct path *path)
+{
+	unsigned int idhashval;
+	unsigned int sc_types;
+
+	sc_types = SC_TYPE_OPEN | SC_TYPE_LOCK | SC_TYPE_DELEG | SC_TYPE_LAYOUT;
+
+	spin_lock(&nn->client_lock);
+	for (idhashval = 0; idhashval < CLIENT_HASH_SIZE; idhashval++) {
+		struct list_head *head = &nn->conf_id_hashtbl[idhashval];
+		struct nfs4_client *clp;
+	retry:
+		list_for_each_entry(clp, head, cl_idhash) {
+			struct nfs4_stid *stid = find_one_export_stid(
+							clp, path,
+							sc_types);
+			if (stid) {
+				spin_unlock(&nn->client_lock);
+				revoke_one_stid(clp, stid);
+				nfs4_put_stid(stid);
+				spin_lock(&nn->client_lock);
+				if (clp->cl_minorversion == 0)
+					nn->nfs40_last_revoke =
+						ktime_get_boottime_seconds();
+				goto retry;
+			}
+		}
+	}
+	spin_unlock(&nn->client_lock);
+}
+
 static inline int
 hash_sessionid(struct nfs4_sessionid *sessionid)
 {
diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index ed0d12f77405..0bbcd5bae340 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -2278,6 +2278,51 @@ int nfsd_nl_unlock_filesystem_doit(struct sk_buff *skb,
 	return error;
 }
 
+/**
+ * nfsd_nl_unlock_export_doit - revoke NFSv4 state for an export path
+ * @skb: reply buffer
+ * @info: netlink metadata and command arguments
+ *
+ * Revokes all NFSv4 state (opens, locks, delegations, layouts) acquired
+ * through any export of the given path, regardless of which client holds
+ * the state.  Userspace (exportfs -u) sends this after removing the last
+ * client for a path so the underlying filesystem can be unmounted.
+ *
+ * Unlike NFSD_CMD_UNLOCK_FILESYSTEM, which operates at superblock
+ * granularity, this command revokes only the state associated with
+ * exports of a specific path.
+ *
+ * Return: 0 on success or a negative errno.
+ */
+int nfsd_nl_unlock_export_doit(struct sk_buff *skb, struct genl_info *info)
+{
+	struct net *net = genl_info_net(info);
+	struct nfsd_net *nn = net_generic(net, nfsd_net_id);
+	struct path path;
+	int error;
+
+	if (GENL_REQ_ATTR_CHECK(info, NFSD_A_UNLOCK_EXPORT_PATH))
+		return -EINVAL;
+
+	trace_nfsd_ctl_unlock_export(net,
+			nla_data(info->attrs[NFSD_A_UNLOCK_EXPORT_PATH]));
+	error = kern_path(
+			nla_data(info->attrs[NFSD_A_UNLOCK_EXPORT_PATH]),
+			0, &path);
+	if (error)
+		return error;
+
+	mutex_lock(&nfsd_mutex);
+	if (nn->nfsd_serv)
+		nfsd4_revoke_export_states(nn, &path);
+	else
+		error = -EINVAL;
+	mutex_unlock(&nfsd_mutex);
+
+	path_put(&path);
+	return error;
+}
+
 /**
  * nfsd_net_init - Prepare the nfsd_net portion of a new net namespace
  * @net: a freshly-created network namespace
diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h
index 7d7e99eeffa5..811c148f36fc 100644
--- a/fs/nfsd/state.h
+++ b/fs/nfsd/state.h
@@ -862,6 +862,7 @@ struct nfsd_file *find_any_file(struct nfs4_file *f);
 
 #ifdef CONFIG_NFSD_V4
 void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb);
+void nfsd4_revoke_export_states(struct nfsd_net *nn, const struct path *path);
 void nfsd4_cancel_copy_by_sb(struct net *net, struct super_block *sb);
 int nfsd_net_cb_init(struct nfsd_net *nn);
 void nfsd_net_cb_shutdown(struct nfsd_net *nn);
@@ -869,6 +870,10 @@ void nfsd_net_cb_shutdown(struct nfsd_net *nn);
 static inline void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb)
 {
 }
+static inline void nfsd4_revoke_export_states(struct nfsd_net *nn,
+					      const struct path *path)
+{
+}
 static inline void nfsd4_cancel_copy_by_sb(struct net *net, struct super_block *sb)
 {
 }
diff --git a/fs/nfsd/trace.h b/fs/nfsd/trace.h
index 976815f6f30f..a13d18447324 100644
--- a/fs/nfsd/trace.h
+++ b/fs/nfsd/trace.h
@@ -2021,6 +2021,25 @@ TRACE_EVENT(nfsd_ctl_unlock_fs,
 	)
 );
 
+TRACE_EVENT(nfsd_ctl_unlock_export,
+	TP_PROTO(
+		const struct net *net,
+		const char *path
+	),
+	TP_ARGS(net, path),
+	TP_STRUCT__entry(
+		__field(unsigned int, netns_ino)
+		__string(path, path)
+	),
+	TP_fast_assign(
+		__entry->netns_ino = net->ns.inum;
+		__assign_str(path);
+	),
+	TP_printk("path=%s",
+		__get_str(path)
+	)
+);
+
 TRACE_EVENT(nfsd_ctl_filehandle,
 	TP_PROTO(
 		const struct net *net,
diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h
index 51f926c0b15b..93dc1c4a28e6 100644
--- a/include/uapi/linux/nfsd_netlink.h
+++ b/include/uapi/linux/nfsd_netlink.h
@@ -95,6 +95,13 @@ enum {
 	NFSD_A_UNLOCK_FILESYSTEM_MAX = (__NFSD_A_UNLOCK_FILESYSTEM_MAX - 1)
 };
 
+enum {
+	NFSD_A_UNLOCK_EXPORT_PATH = 1,
+
+	__NFSD_A_UNLOCK_EXPORT_MAX,
+	NFSD_A_UNLOCK_EXPORT_MAX = (__NFSD_A_UNLOCK_EXPORT_MAX - 1)
+};
+
 enum {
 	NFSD_CMD_RPC_STATUS_GET = 1,
 	NFSD_CMD_THREADS_SET,
@@ -107,6 +114,7 @@ enum {
 	NFSD_CMD_POOL_MODE_GET,
 	NFSD_CMD_UNLOCK_IP,
 	NFSD_CMD_UNLOCK_FILESYSTEM,
+	NFSD_CMD_UNLOCK_EXPORT,
 
 	__NFSD_CMD_MAX,
 	NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v5 7/7] NFSD: Close cached file handles when revoking export state
  2026-03-26 17:55 [PATCH v5 0/7] Automatic NFSv4 state revocation on filesystem unmount Chuck Lever
                   ` (5 preceding siblings ...)
  2026-03-26 17:55 ` [PATCH v5 6/7] NFSD: Add NFSD_CMD_UNLOCK_EXPORT netlink command Chuck Lever
@ 2026-03-26 17:55 ` Chuck Lever
  2026-03-27 12:03 ` [PATCH v5 0/7] Automatic NFSv4 state revocation on filesystem unmount Jeff Layton
  2026-03-27 12:18 ` Jeff Layton
  8 siblings, 0 replies; 22+ messages in thread
From: Chuck Lever @ 2026-03-26 17:55 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey
  Cc: linux-nfs, linux-fsdevel, Chuck Lever

From: Chuck Lever <chuck.lever@oracle.com>

When NFSD_CMD_UNLOCK_EXPORT revokes NFSv4 state for an export path,
GC-managed nfsd_file entries for files under that path may remain
in the file cache.  These cached handles hold the underlying
filesystem busy, preventing a subsequent unmount.

Add nfsd_file_close_export(), which walks the nfsd_file hash table
and closes GC-eligible entries whose underlying file resides on the
same filesystem and is a descendant of the export path.  Because
nfsd_file entries do not carry an export reference, the ancestry
check uses is_subdir() on the file's dentry.  False positives --
closing a cached handle that did not originate from the target
export -- are harmless; the handle is simply reopened on the next
access.

The handler calls nfsd_file_close_export() before revoking NFSv4
state, mirroring the order used by NFSD_CMD_UNLOCK_FILESYSTEM
(which cancels copies and releases NLM locks before revoking
state).  Both calls run under nfsd_mutex.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
 fs/nfsd/filecache.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++
 fs/nfsd/filecache.h |  1 +
 fs/nfsd/nfsctl.c    |  5 +++--
 3 files changed, 50 insertions(+), 2 deletions(-)

diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c
index 1e2b38ed1d35..24511c3208db 100644
--- a/fs/nfsd/filecache.c
+++ b/fs/nfsd/filecache.c
@@ -724,6 +724,52 @@ nfsd_file_close_inode_sync(struct inode *inode)
 	nfsd_file_dispose_list(&dispose);
 }
 
+/**
+ * nfsd_file_close_export - close cached file handles for an export
+ * @net: net namespace in which to operate
+ * @path: export path whose cached files should be closed
+ *
+ * Close out GC-managed nfsd_file entries whose underlying file is on
+ * the same filesystem as, and a descendant of, @path.  nfsd_file
+ * entries do not carry an export reference, so the check uses the
+ * file's dentry ancestry.  False positives (closing a cached handle
+ * that did not originate from the target export) are harmless -- the
+ * handle is simply reopened on the next access.
+ *
+ * Called from the NFSD_CMD_UNLOCK_EXPORT handler before revoking
+ * NFSv4 state, to ensure that cached file handles do not hold the
+ * filesystem busy.
+ */
+void nfsd_file_close_export(struct net *net, const struct path *path)
+{
+	struct rhashtable_iter iter;
+	struct nfsd_file *nf;
+	LIST_HEAD(dispose);
+
+	rhltable_walk_enter(&nfsd_file_rhltable, &iter);
+	do {
+		rhashtable_walk_start(&iter);
+
+		nf = rhashtable_walk_next(&iter);
+		while (!IS_ERR_OR_NULL(nf)) {
+			if (nf->nf_net == net &&
+			    test_bit(NFSD_FILE_GC, &nf->nf_flags) &&
+			    nf->nf_file &&
+			    file_inode(nf->nf_file)->i_sb ==
+					path->dentry->d_sb &&
+			    is_subdir(nf->nf_file->f_path.dentry,
+				      path->dentry))
+				nfsd_file_cond_queue(nf, &dispose);
+			nf = rhashtable_walk_next(&iter);
+		}
+
+		rhashtable_walk_stop(&iter);
+	} while (nf == ERR_PTR(-EAGAIN));
+	rhashtable_walk_exit(&iter);
+
+	nfsd_file_dispose_list(&dispose);
+}
+
 static int
 nfsd_file_lease_notifier_call(struct notifier_block *nb, unsigned long arg,
 			    void *data)
diff --git a/fs/nfsd/filecache.h b/fs/nfsd/filecache.h
index b383dbc5b921..683b6437cacc 100644
--- a/fs/nfsd/filecache.h
+++ b/fs/nfsd/filecache.h
@@ -70,6 +70,7 @@ struct net *nfsd_file_put_local(struct nfsd_file __rcu **nf);
 struct nfsd_file *nfsd_file_get(struct nfsd_file *nf);
 struct file *nfsd_file_file(struct nfsd_file *nf);
 void nfsd_file_close_inode_sync(struct inode *inode);
+void nfsd_file_close_export(struct net *net, const struct path *path);
 void nfsd_file_net_dispose(struct nfsd_net *nn);
 bool nfsd_file_is_cached(struct inode *inode);
 __be32 nfsd_file_acquire_gc(struct svc_rqst *rqstp, struct svc_fh *fhp,
diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index 0bbcd5bae340..8aef18a0c0f2 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -2313,9 +2313,10 @@ int nfsd_nl_unlock_export_doit(struct sk_buff *skb, struct genl_info *info)
 		return error;
 
 	mutex_lock(&nfsd_mutex);
-	if (nn->nfsd_serv)
+	if (nn->nfsd_serv) {
+		nfsd_file_close_export(net, &path);
 		nfsd4_revoke_export_states(nn, &path);
-	else
+	} else
 		error = -EINVAL;
 	mutex_unlock(&nfsd_mutex);
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* Re: [PATCH v5 1/7] NFSD: Extract revoke_one_stid() utility function
  2026-03-26 17:55 ` [PATCH v5 1/7] NFSD: Extract revoke_one_stid() utility function Chuck Lever
@ 2026-03-27  5:26   ` kernel test robot
  2026-03-27  5:56   ` kernel test robot
                     ` (4 subsequent siblings)
  5 siblings, 0 replies; 22+ messages in thread
From: kernel test robot @ 2026-03-27  5:26 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo,
	Tom Talpey
  Cc: oe-kbuild-all, linux-nfs, linux-fsdevel, Chuck Lever

Hi Chuck,

kernel test robot noticed the following build errors:

[auto build test ERROR on 65058e9e9b20619f920397f529072e853dd43811]

url:    https://github.com/intel-lab-lkp/linux/commits/Chuck-Lever/NFSD-Extract-revoke_one_stid-utility-function/20260327-081757
base:   65058e9e9b20619f920397f529072e853dd43811
patch link:    https://lore.kernel.org/r/20260326-umount-kills-nfsv4-state-v5-1-d2ce071b3570%40oracle.com
patch subject: [PATCH v5 1/7] NFSD: Extract revoke_one_stid() utility function
config: x86_64-rhel-9.4-ltp (https://download.01.org/0day-ci/archive/20260327/202603270614.CgH26mor-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260327/202603270614.CgH26mor-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603270614.CgH26mor-lkp@intel.com/

All errors (new ones prefixed by >>):

   fs/nfsd/nfs4state.c: In function 'revoke_one_stid':
>> fs/nfsd/nfs4state.c:1830:28: error: 'state_lock' undeclared (first use in this function); did you mean 'task_lock'?
    1830 |                 spin_lock(&state_lock);
         |                            ^~~~~~~~~~
         |                            task_lock
   fs/nfsd/nfs4state.c:1830:28: note: each undeclared identifier is reported only once for each function it appears in


vim +1830 fs/nfsd/nfs4state.c

  1805	
  1806	static void revoke_one_stid(struct nfs4_client *clp, struct nfs4_stid *stid)
  1807	{
  1808		struct nfs4_ol_stateid *stp;
  1809		struct nfs4_delegation *dp;
  1810	
  1811		switch (stid->sc_type) {
  1812		case SC_TYPE_OPEN:
  1813			stp = openlockstateid(stid);
  1814			mutex_lock_nested(&stp->st_mutex, OPEN_STATEID_MUTEX);
  1815			revoke_ol_stid(clp, stp);
  1816			mutex_unlock(&stp->st_mutex);
  1817			break;
  1818		case SC_TYPE_LOCK:
  1819			stp = openlockstateid(stid);
  1820			mutex_lock_nested(&stp->st_mutex, LOCK_STATEID_MUTEX);
  1821			revoke_ol_stid(clp, stp);
  1822			mutex_unlock(&stp->st_mutex);
  1823			break;
  1824		case SC_TYPE_DELEG:
  1825			/*
  1826			 * Extra reference guards against concurrent FREE_STATEID.
  1827			 */
  1828			refcount_inc(&stid->sc_count);
  1829			dp = delegstateid(stid);
> 1830			spin_lock(&state_lock);
  1831			if (!unhash_delegation_locked(dp, SC_STATUS_ADMIN_REVOKED))
  1832				dp = NULL;
  1833			spin_unlock(&state_lock);
  1834			if (dp)
  1835				revoke_delegation(dp);
  1836			else
  1837				nfs4_put_stid(stid);
  1838			break;
  1839		case SC_TYPE_LAYOUT:
  1840			nfsd4_close_layout(layoutstateid(stid));
  1841			break;
  1842		}
  1843	}
  1844	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v5 1/7] NFSD: Extract revoke_one_stid() utility function
  2026-03-26 17:55 ` [PATCH v5 1/7] NFSD: Extract revoke_one_stid() utility function Chuck Lever
  2026-03-27  5:26   ` kernel test robot
@ 2026-03-27  5:56   ` kernel test robot
  2026-03-27 10:08   ` kernel test robot
                     ` (3 subsequent siblings)
  5 siblings, 0 replies; 22+ messages in thread
From: kernel test robot @ 2026-03-27  5:56 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo,
	Tom Talpey
  Cc: llvm, oe-kbuild-all, linux-nfs, linux-fsdevel, Chuck Lever

Hi Chuck,

kernel test robot noticed the following build errors:

[auto build test ERROR on 65058e9e9b20619f920397f529072e853dd43811]

url:    https://github.com/intel-lab-lkp/linux/commits/Chuck-Lever/NFSD-Extract-revoke_one_stid-utility-function/20260327-081757
base:   65058e9e9b20619f920397f529072e853dd43811
patch link:    https://lore.kernel.org/r/20260326-umount-kills-nfsv4-state-v5-1-d2ce071b3570%40oracle.com
patch subject: [PATCH v5 1/7] NFSD: Extract revoke_one_stid() utility function
config: x86_64-kexec (https://download.01.org/0day-ci/archive/20260327/202603270629.lOb5BcMW-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260327/202603270629.lOb5BcMW-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603270629.lOb5BcMW-lkp@intel.com/

All errors (new ones prefixed by >>):

>> fs/nfsd/nfs4state.c:1830:14: error: use of undeclared identifier 'state_lock'; did you mean 'task_lock'?
    1830 |                 spin_lock(&state_lock);
         |                            ^~~~~~~~~~
         |                            task_lock
   include/linux/sched/task.h:216:20: note: 'task_lock' declared here
     216 | static inline void task_lock(struct task_struct *p)
         |                    ^
   fs/nfsd/nfs4state.c:1833:16: error: use of undeclared identifier 'state_lock'; did you mean 'task_lock'?
    1833 |                 spin_unlock(&state_lock);
         |                              ^~~~~~~~~~
         |                              task_lock
   include/linux/sched/task.h:216:20: note: 'task_lock' declared here
     216 | static inline void task_lock(struct task_struct *p)
         |                    ^
   2 errors generated.


vim +1830 fs/nfsd/nfs4state.c

  1805	
  1806	static void revoke_one_stid(struct nfs4_client *clp, struct nfs4_stid *stid)
  1807	{
  1808		struct nfs4_ol_stateid *stp;
  1809		struct nfs4_delegation *dp;
  1810	
  1811		switch (stid->sc_type) {
  1812		case SC_TYPE_OPEN:
  1813			stp = openlockstateid(stid);
  1814			mutex_lock_nested(&stp->st_mutex, OPEN_STATEID_MUTEX);
  1815			revoke_ol_stid(clp, stp);
  1816			mutex_unlock(&stp->st_mutex);
  1817			break;
  1818		case SC_TYPE_LOCK:
  1819			stp = openlockstateid(stid);
  1820			mutex_lock_nested(&stp->st_mutex, LOCK_STATEID_MUTEX);
  1821			revoke_ol_stid(clp, stp);
  1822			mutex_unlock(&stp->st_mutex);
  1823			break;
  1824		case SC_TYPE_DELEG:
  1825			/*
  1826			 * Extra reference guards against concurrent FREE_STATEID.
  1827			 */
  1828			refcount_inc(&stid->sc_count);
  1829			dp = delegstateid(stid);
> 1830			spin_lock(&state_lock);
  1831			if (!unhash_delegation_locked(dp, SC_STATUS_ADMIN_REVOKED))
  1832				dp = NULL;
  1833			spin_unlock(&state_lock);
  1834			if (dp)
  1835				revoke_delegation(dp);
  1836			else
  1837				nfs4_put_stid(stid);
  1838			break;
  1839		case SC_TYPE_LAYOUT:
  1840			nfsd4_close_layout(layoutstateid(stid));
  1841			break;
  1842		}
  1843	}
  1844	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v5 1/7] NFSD: Extract revoke_one_stid() utility function
  2026-03-26 17:55 ` [PATCH v5 1/7] NFSD: Extract revoke_one_stid() utility function Chuck Lever
  2026-03-27  5:26   ` kernel test robot
  2026-03-27  5:56   ` kernel test robot
@ 2026-03-27 10:08   ` kernel test robot
  2026-03-27 11:13   ` kernel test robot
                     ` (2 subsequent siblings)
  5 siblings, 0 replies; 22+ messages in thread
From: kernel test robot @ 2026-03-27 10:08 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo,
	Tom Talpey
  Cc: llvm, oe-kbuild-all, linux-nfs, linux-fsdevel, Chuck Lever

Hi Chuck,

kernel test robot noticed the following build errors:

[auto build test ERROR on 65058e9e9b20619f920397f529072e853dd43811]

url:    https://github.com/intel-lab-lkp/linux/commits/Chuck-Lever/NFSD-Extract-revoke_one_stid-utility-function/20260327-081757
base:   65058e9e9b20619f920397f529072e853dd43811
patch link:    https://lore.kernel.org/r/20260326-umount-kills-nfsv4-state-v5-1-d2ce071b3570%40oracle.com
patch subject: [PATCH v5 1/7] NFSD: Extract revoke_one_stid() utility function
config: s390-defconfig (https://download.01.org/0day-ci/archive/20260327/202603271711.Pbg3v6zE-lkp@intel.com/config)
compiler: clang version 23.0.0git (https://github.com/llvm/llvm-project 054e11d1a17e5ba88bb1a8ef32fad3346e80b186)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260327/202603271711.Pbg3v6zE-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603271711.Pbg3v6zE-lkp@intel.com/

All errors (new ones prefixed by >>):

   fs/nfsd/nfs4state.c:1830:14: error: use of undeclared identifier 'state_lock'; did you mean 'task_lock'?
    1830 |                 spin_lock(&state_lock);
         |                            ^~~~~~~~~~
         |                            task_lock
   include/linux/sched/task.h:216:20: note: 'task_lock' declared here
     216 | static inline void task_lock(struct task_struct *p)
         |                    ^
>> fs/nfsd/nfs4state.c:1830:13: error: incompatible pointer types passing 'void (*)(struct task_struct *)' to parameter of type 'spinlock_t *' (aka 'struct spinlock *') [-Wincompatible-pointer-types]
    1830 |                 spin_lock(&state_lock);
         |                           ^~~~~~~~~~~
   include/linux/spinlock.h:338:51: note: passing argument to parameter 'lock' here
     338 | static __always_inline void spin_lock(spinlock_t *lock)
         |                                                   ^
   fs/nfsd/nfs4state.c:1833:16: error: use of undeclared identifier 'state_lock'; did you mean 'task_lock'?
    1833 |                 spin_unlock(&state_lock);
         |                              ^~~~~~~~~~
         |                              task_lock
   include/linux/sched/task.h:216:20: note: 'task_lock' declared here
     216 | static inline void task_lock(struct task_struct *p)
         |                    ^
   fs/nfsd/nfs4state.c:1833:15: error: incompatible pointer types passing 'void (*)(struct task_struct *)' to parameter of type 'spinlock_t *' (aka 'struct spinlock *') [-Wincompatible-pointer-types]
    1833 |                 spin_unlock(&state_lock);
         |                             ^~~~~~~~~~~
   include/linux/spinlock.h:386:53: note: passing argument to parameter 'lock' here
     386 | static __always_inline void spin_unlock(spinlock_t *lock)
         |                                                     ^
   4 errors generated.


vim +1830 fs/nfsd/nfs4state.c

  1805	
  1806	static void revoke_one_stid(struct nfs4_client *clp, struct nfs4_stid *stid)
  1807	{
  1808		struct nfs4_ol_stateid *stp;
  1809		struct nfs4_delegation *dp;
  1810	
  1811		switch (stid->sc_type) {
  1812		case SC_TYPE_OPEN:
  1813			stp = openlockstateid(stid);
  1814			mutex_lock_nested(&stp->st_mutex, OPEN_STATEID_MUTEX);
  1815			revoke_ol_stid(clp, stp);
  1816			mutex_unlock(&stp->st_mutex);
  1817			break;
  1818		case SC_TYPE_LOCK:
  1819			stp = openlockstateid(stid);
  1820			mutex_lock_nested(&stp->st_mutex, LOCK_STATEID_MUTEX);
  1821			revoke_ol_stid(clp, stp);
  1822			mutex_unlock(&stp->st_mutex);
  1823			break;
  1824		case SC_TYPE_DELEG:
  1825			/*
  1826			 * Extra reference guards against concurrent FREE_STATEID.
  1827			 */
  1828			refcount_inc(&stid->sc_count);
  1829			dp = delegstateid(stid);
> 1830			spin_lock(&state_lock);
  1831			if (!unhash_delegation_locked(dp, SC_STATUS_ADMIN_REVOKED))
  1832				dp = NULL;
  1833			spin_unlock(&state_lock);
  1834			if (dp)
  1835				revoke_delegation(dp);
  1836			else
  1837				nfs4_put_stid(stid);
  1838			break;
  1839		case SC_TYPE_LAYOUT:
  1840			nfsd4_close_layout(layoutstateid(stid));
  1841			break;
  1842		}
  1843	}
  1844	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v5 1/7] NFSD: Extract revoke_one_stid() utility function
  2026-03-26 17:55 ` [PATCH v5 1/7] NFSD: Extract revoke_one_stid() utility function Chuck Lever
                     ` (2 preceding siblings ...)
  2026-03-27 10:08   ` kernel test robot
@ 2026-03-27 11:13   ` kernel test robot
  2026-03-27 12:00   ` Jeff Layton
  2026-03-27 12:21   ` Jeff Layton
  5 siblings, 0 replies; 22+ messages in thread
From: kernel test robot @ 2026-03-27 11:13 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo,
	Tom Talpey
  Cc: llvm, oe-kbuild-all, linux-nfs, linux-fsdevel, Chuck Lever

Hi Chuck,

kernel test robot noticed the following build errors:

[auto build test ERROR on 65058e9e9b20619f920397f529072e853dd43811]

url:    https://github.com/intel-lab-lkp/linux/commits/Chuck-Lever/NFSD-Extract-revoke_one_stid-utility-function/20260327-081757
base:   65058e9e9b20619f920397f529072e853dd43811
patch link:    https://lore.kernel.org/r/20260326-umount-kills-nfsv4-state-v5-1-d2ce071b3570%40oracle.com
patch subject: [PATCH v5 1/7] NFSD: Extract revoke_one_stid() utility function
config: loongarch-defconfig (https://download.01.org/0day-ci/archive/20260327/202603271905.vZdE0ulk-lkp@intel.com/config)
compiler: clang version 19.1.7 (https://github.com/llvm/llvm-project cd708029e0b2869e80abe31ddb175f7c35361f90)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260327/202603271905.vZdE0ulk-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603271905.vZdE0ulk-lkp@intel.com/

All errors (new ones prefixed by >>):

>> fs/nfsd/nfs4state.c:1830:14: error: use of undeclared identifier 'state_lock'; did you mean 'task_lock'?
    1830 |                 spin_lock(&state_lock);
         |                            ^~~~~~~~~~
         |                            task_lock
   include/linux/sched/task.h:216:20: note: 'task_lock' declared here
     216 | static inline void task_lock(struct task_struct *p)
         |                    ^
   fs/nfsd/nfs4state.c:1833:16: error: use of undeclared identifier 'state_lock'; did you mean 'task_lock'?
    1833 |                 spin_unlock(&state_lock);
         |                              ^~~~~~~~~~
         |                              task_lock
   include/linux/sched/task.h:216:20: note: 'task_lock' declared here
     216 | static inline void task_lock(struct task_struct *p)
         |                    ^
   2 errors generated.


vim +1830 fs/nfsd/nfs4state.c

  1805	
  1806	static void revoke_one_stid(struct nfs4_client *clp, struct nfs4_stid *stid)
  1807	{
  1808		struct nfs4_ol_stateid *stp;
  1809		struct nfs4_delegation *dp;
  1810	
  1811		switch (stid->sc_type) {
  1812		case SC_TYPE_OPEN:
  1813			stp = openlockstateid(stid);
  1814			mutex_lock_nested(&stp->st_mutex, OPEN_STATEID_MUTEX);
  1815			revoke_ol_stid(clp, stp);
  1816			mutex_unlock(&stp->st_mutex);
  1817			break;
  1818		case SC_TYPE_LOCK:
  1819			stp = openlockstateid(stid);
  1820			mutex_lock_nested(&stp->st_mutex, LOCK_STATEID_MUTEX);
  1821			revoke_ol_stid(clp, stp);
  1822			mutex_unlock(&stp->st_mutex);
  1823			break;
  1824		case SC_TYPE_DELEG:
  1825			/*
  1826			 * Extra reference guards against concurrent FREE_STATEID.
  1827			 */
  1828			refcount_inc(&stid->sc_count);
  1829			dp = delegstateid(stid);
> 1830			spin_lock(&state_lock);
  1831			if (!unhash_delegation_locked(dp, SC_STATUS_ADMIN_REVOKED))
  1832				dp = NULL;
  1833			spin_unlock(&state_lock);
  1834			if (dp)
  1835				revoke_delegation(dp);
  1836			else
  1837				nfs4_put_stid(stid);
  1838			break;
  1839		case SC_TYPE_LAYOUT:
  1840			nfsd4_close_layout(layoutstateid(stid));
  1841			break;
  1842		}
  1843	}
  1844	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v5 1/7] NFSD: Extract revoke_one_stid() utility function
  2026-03-26 17:55 ` [PATCH v5 1/7] NFSD: Extract revoke_one_stid() utility function Chuck Lever
                     ` (3 preceding siblings ...)
  2026-03-27 11:13   ` kernel test robot
@ 2026-03-27 12:00   ` Jeff Layton
  2026-03-27 12:21   ` Jeff Layton
  5 siblings, 0 replies; 22+ messages in thread
From: Jeff Layton @ 2026-03-27 12:00 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
  Cc: linux-nfs, linux-fsdevel, Chuck Lever

On Thu, 2026-03-26 at 13:55 -0400, Chuck Lever wrote:
> From: Chuck Lever <chuck.lever@oracle.com>
> 
> The per-stateid revocation logic in nfsd4_revoke_states() handles
> four stateid types in a deeply nested switch. Extract two helpers:
> 
> revoke_ol_stid() performs admin-revocation of an open or lock
> stateid with st_mutex already held: marks the stateid as
> SC_STATUS_ADMIN_REVOKED, closes POSIX locks for lock stateids,
> and releases file access.
> 
> revoke_one_stid() dispatches by sc_type, acquires st_mutex with
> the appropriate lockdep class for open and lock stateids, and
> handles delegation unhash and layout close inline.
> 
> No functional change. Preparation for adding export-scoped state
> revocation which reuses revoke_one_stid().
> 
> Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
> ---
>  fs/nfsd/nfs4state.c | 137 ++++++++++++++++++++++++++--------------------------
>  1 file changed, 68 insertions(+), 69 deletions(-)
> 
> diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
> index 07df4511ba23..eb1bd1aae8f5 100644
> --- a/fs/nfsd/nfs4state.c
> +++ b/fs/nfsd/nfs4state.c
> @@ -1775,6 +1775,73 @@ static struct nfs4_stid *find_one_sb_stid(struct nfs4_client *clp,
>  	return stid;
>  }
>  
> +static void revoke_ol_stid(struct nfs4_client *clp,
> +			   struct nfs4_ol_stateid *stp)
> +{
> +	struct nfs4_stid *stid = &stp->st_stid;
> +
> +	lockdep_assert_held(&stp->st_mutex);
> +	spin_lock(&clp->cl_lock);
> +	if (stid->sc_status == 0) {
> +		stid->sc_status |= SC_STATUS_ADMIN_REVOKED;
> +		atomic_inc(&clp->cl_admin_revoked);
> +		spin_unlock(&clp->cl_lock);
> +		if (stid->sc_type == SC_TYPE_LOCK) {
> +			struct nfs4_lockowner *lo =
> +				lockowner(stp->st_stateowner);
> +			struct nfsd_file *nf;
> +
> +			nf = find_any_file(stp->st_stid.sc_file);
> +			if (nf) {
> +				get_file(nf->nf_file);
> +				filp_close(nf->nf_file, (fl_owner_t)lo);
> +				nfsd_file_put(nf);
> +			}
> +		}
> +		release_all_access(stp);
> +	} else
> +		spin_unlock(&clp->cl_lock);
> +}
> +
> +static void revoke_one_stid(struct nfs4_client *clp, struct nfs4_stid *stid)
> +{
> +	struct nfs4_ol_stateid *stp;
> +	struct nfs4_delegation *dp;
> +
> +	switch (stid->sc_type) {
> +	case SC_TYPE_OPEN:
> +		stp = openlockstateid(stid);
> +		mutex_lock_nested(&stp->st_mutex, OPEN_STATEID_MUTEX);
> +		revoke_ol_stid(clp, stp);
> +		mutex_unlock(&stp->st_mutex);
> +		break;
> +	case SC_TYPE_LOCK:
> +		stp = openlockstateid(stid);
> +		mutex_lock_nested(&stp->st_mutex, LOCK_STATEID_MUTEX);
> +		revoke_ol_stid(clp, stp);
> +		mutex_unlock(&stp->st_mutex);
> +		break;
> +	case SC_TYPE_DELEG:
> +		/*
> +		 * Extra reference guards against concurrent FREE_STATEID.
> +		 */
> +		refcount_inc(&stid->sc_count);
> +		dp = delegstateid(stid);
> +		spin_lock(&state_lock);
> +		if (!unhash_delegation_locked(dp, SC_STATUS_ADMIN_REVOKED))
> +			dp = NULL;
> +		spin_unlock(&state_lock);
> +		if (dp)
> +			revoke_delegation(dp);
> +		else
> +			nfs4_put_stid(stid);
> +		break;
> +	case SC_TYPE_LAYOUT:
> +		nfsd4_close_layout(layoutstateid(stid));
> +		break;
> +	}
> +}
> +
>  /**
>   * nfsd4_revoke_states - revoke all nfsv4 states associated with given filesystem
>   * @nn:   used to identify instance of nfsd (there is one per net namespace)
> @@ -1805,76 +1872,8 @@ void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb)
>  			struct nfs4_stid *stid = find_one_sb_stid(clp, sb,
>  								  sc_types);
>  			if (stid) {
> -				struct nfs4_ol_stateid *stp;
> -				struct nfs4_delegation *dp;
> -				struct nfs4_layout_stateid *ls;
> -
>  				spin_unlock(&nn->client_lock);
> -				switch (stid->sc_type) {
> -				case SC_TYPE_OPEN:
> -					stp = openlockstateid(stid);
> -					mutex_lock_nested(&stp->st_mutex,
> -							  OPEN_STATEID_MUTEX);
> -
> -					spin_lock(&clp->cl_lock);
> -					if (stid->sc_status == 0) {
> -						stid->sc_status |=
> -							SC_STATUS_ADMIN_REVOKED;
> -						atomic_inc(&clp->cl_admin_revoked);
> -						spin_unlock(&clp->cl_lock);
> -						release_all_access(stp);
> -					} else
> -						spin_unlock(&clp->cl_lock);
> -					mutex_unlock(&stp->st_mutex);
> -					break;
> -				case SC_TYPE_LOCK:
> -					stp = openlockstateid(stid);
> -					mutex_lock_nested(&stp->st_mutex,
> -							  LOCK_STATEID_MUTEX);
> -					spin_lock(&clp->cl_lock);
> -					if (stid->sc_status == 0) {
> -						struct nfs4_lockowner *lo =
> -							lockowner(stp->st_stateowner);
> -						struct nfsd_file *nf;
> -
> -						stid->sc_status |=
> -							SC_STATUS_ADMIN_REVOKED;
> -						atomic_inc(&clp->cl_admin_revoked);
> -						spin_unlock(&clp->cl_lock);
> -						nf = find_any_file(stp->st_stid.sc_file);
> -						if (nf) {
> -							get_file(nf->nf_file);
> -							filp_close(nf->nf_file,
> -								   (fl_owner_t)lo);
> -							nfsd_file_put(nf);
> -						}
> -						release_all_access(stp);
> -					} else
> -						spin_unlock(&clp->cl_lock);
> -					mutex_unlock(&stp->st_mutex);
> -					break;
> -				case SC_TYPE_DELEG:
> -					/* Extra reference guards against concurrent
> -					 * FREE_STATEID; revoke_delegation() consumes
> -					 * it, otherwise release it directly.
> -					 */
> -					refcount_inc(&stid->sc_count);
> -					dp = delegstateid(stid);
> -					spin_lock(&nn->deleg_lock);
> -					if (!unhash_delegation_locked(
> -						    dp, SC_STATUS_ADMIN_REVOKED))
> -						dp = NULL;
> -					spin_unlock(&nn->deleg_lock);
> -					if (dp)
> -						revoke_delegation(dp);
> -					else
> -						nfs4_put_stid(stid);
> -					break;
> -				case SC_TYPE_LAYOUT:
> -					ls = layoutstateid(stid);
> -					nfsd4_close_layout(ls);
> -					break;
> -				}
> +				revoke_one_stid(clp, stid);
>  				nfs4_put_stid(stid);
>  				spin_lock(&nn->client_lock);
>  				if (clp->cl_minorversion == 0)

Nice little cleanup on its own. This could be merged ahead of the rest
of the series.

Reviewed-by: Jeff Layton <jlayton@kernel.org>

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v5 0/7] Automatic NFSv4 state revocation on filesystem unmount
  2026-03-26 17:55 [PATCH v5 0/7] Automatic NFSv4 state revocation on filesystem unmount Chuck Lever
                   ` (6 preceding siblings ...)
  2026-03-26 17:55 ` [PATCH v5 7/7] NFSD: Close cached file handles when revoking export state Chuck Lever
@ 2026-03-27 12:03 ` Jeff Layton
  2026-03-27 13:29   ` Chuck Lever
  2026-03-27 12:18 ` Jeff Layton
  8 siblings, 1 reply; 22+ messages in thread
From: Jeff Layton @ 2026-03-27 12:03 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
  Cc: linux-nfs, linux-fsdevel, Chuck Lever

On Thu, 2026-03-26 at 13:55 -0400, Chuck Lever wrote:
> When an NFS server exports a filesystem and clients hold NFSv4
> state (opens, locks, delegations), unmounting the underlying
> filesystem fails with EBUSY. The /proc/fs/nfsd/unlock_ip and
> /proc/fs/nfsd/unlock_fs procfs interfaces handle this, but have
> no netlink equivalents, and unlock_fs operates at whole-superblock
> granularity.
> 
> This series adds three new NFSD netlink commands, each with its own
> attribute set:
> 
>  - NFSD_CMD_UNLOCK_IP releases NLM locks held by a client IP
>    address. Netlink equivalent of write_unlock_ip.
> 
>  - NFSD_CMD_UNLOCK_FILESYSTEM revokes all NFS state on a
>    superblock. Netlink equivalent of write_unlock_fs.
> 
>  - NFSD_CMD_UNLOCK_EXPORT revokes NFSv4 state acquired through
>    exports of a specific path, regardless of client.
> 
> UNLOCK_FILESYSTEM and UNLOCK_EXPORT serve different intents.
> UNLOCK_FILESYSTEM means "unmounting /data, release everything
> on this superblock." UNLOCK_EXPORT means "no clients remain for
> /data/projectA, release only the state acquired through exports
> of that path." Userspace (exportfs -u) sends UNLOCK_EXPORT after
> removing the last client for a given path, enabling the underlying
> filesystem to be unmounted.
> 
> The path-only design for UNLOCK_EXPORT avoids the auth_domain
> naming complexity (use_ipaddr vs hostname-based domains) by not
> requiring the caller to identify a specific client. Since this
> mechanism is to be used to enable umount, this seemed like a
> reasonable compromise.
> 

Can you comment a bit about your intentions with userland here? Do you
plan to extend nfsdctl to add this support or add a new binary to nfs-
utils? Do you have userland patches or is that still TBD?


> ---
> Changes since v4:
> - 1/9 has been queued in nfsd-testing
> - Split single NFSD_CMD_UNLOCK into three separate commands
> - UNLOCK_EXPORT takes path only, no client attribute to avoid
>   auth_domain naming complexity with use_ipaddr
> 
> Changes since v3:
> - All VFS changes replaced with new netlink "unlock" operation
> 
> Changes since v2:
> - Replace fs_pin with an SRCU umount notifier chain in VFS
> - Merge the pending COPY cancellation patch
> - Replace xa_cmpxchg() with xa_insert()
> - Use cancel_work_sync() instead of flush_workqueue()
> - Remove rcu_barrier()
> - Correct misleading claims in kdoc comments and commit messages
> 
> Changes since v1:
> - Explain why drop_client() is being renamed
> - Finish implementing revocation on umount
> - Rename pin_insert_group
> - Clarified log output and code comments
> - Hold nfsd_mutex while closing nfsd_files
> 
> ---
> Chuck Lever (7):
>       NFSD: Extract revoke_one_stid() utility function
>       NFSD: Add NFSD_CMD_UNLOCK_IP netlink command
>       NFSD: Add NFSD_CMD_UNLOCK_FILESYSTEM netlink command
>       NFSD: Replace idr_for_each_entry_ul in find_one_sb_stid()
>       NFSD: Track svc_export in nfs4_stid
>       NFSD: Add NFSD_CMD_UNLOCK_EXPORT netlink command
>       NFSD: Close cached file handles when revoking export state
> 
>  Documentation/netlink/specs/nfsd.yaml |  61 +++++++++
>  fs/nfsd/filecache.c                   |  46 +++++++
>  fs/nfsd/filecache.h                   |   1 +
>  fs/nfsd/netlink.c                     |  36 ++++++
>  fs/nfsd/netlink.h                     |   3 +
>  fs/nfsd/nfs4layouts.c                 |   2 +
>  fs/nfsd/nfs4state.c                   | 226 +++++++++++++++++++++++-----------
>  fs/nfsd/nfsctl.c                      | 126 ++++++++++++++++++-
>  fs/nfsd/state.h                       |   6 +
>  fs/nfsd/trace.h                       |  32 ++++-
>  include/uapi/linux/nfsd_netlink.h     |  24 ++++
>  11 files changed, 484 insertions(+), 79 deletions(-)
> ---
> base-commit: 65058e9e9b20619f920397f529072e853dd43811
> change-id: 20260318-umount-kills-nfsv4-state-138218f2f4e0
> 
> Best regards,
> --  
> Chuck Lever

-- 
Jeff Layton <jlayton@kernel.org>

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v5 2/7] NFSD: Add NFSD_CMD_UNLOCK_IP netlink command
  2026-03-26 17:55 ` [PATCH v5 2/7] NFSD: Add NFSD_CMD_UNLOCK_IP netlink command Chuck Lever
@ 2026-03-27 12:06   ` Jeff Layton
  2026-03-27 15:19     ` Chuck Lever
  0 siblings, 1 reply; 22+ messages in thread
From: Jeff Layton @ 2026-03-27 12:06 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
  Cc: linux-nfs, linux-fsdevel, Chuck Lever

On Thu, 2026-03-26 at 13:55 -0400, Chuck Lever wrote:
> From: Chuck Lever <chuck.lever@oracle.com>
> 
> The existing write_unlock_ip procfs interface releases NLM file
> locks held by a specific client IP address, but procfs provides
> no structured way to extend that operation to other scopes such as
> revoking NFSv4 state.
> 
> Add NFSD_CMD_UNLOCK_IP as a dedicated netlink command for
> releasing NLM locks by client address. The command accepts a
> binary sockaddr_in or sockaddr_in6 in its address attribute.
> The handler validates the address family and length, then calls
> nlmsvc_unlock_all_by_ip() to release matching NLM locks.  Because
> lockd is a single global instance, that call operates across
> all network namespaces regardless of which namespace the caller
> inhabits.
> 
> A separate netlink command for filesystem-scoped unlock is added in
> a subsequent commit.
> 
> The nfsd_ctl_unlock_ip tracepoint is updated from string-based
> address logging to __sockaddr, which stores the binary sockaddr
> and formats it with %pISpc. This affects both the new netlink path
> and the existing procfs write_unlock_ip path, giving consistent
> structured output in both cases.
> 
> Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
> ---
>  Documentation/netlink/specs/nfsd.yaml | 18 ++++++++++++++++
>  fs/nfsd/netlink.c                     | 12 +++++++++++
>  fs/nfsd/netlink.h                     |  1 +
>  fs/nfsd/nfsctl.c                      | 40 ++++++++++++++++++++++++++++++++++-
>  fs/nfsd/trace.h                       | 13 ++++++------
>  include/uapi/linux/nfsd_netlink.h     |  8 +++++++
>  6 files changed, 85 insertions(+), 7 deletions(-)
> 
> diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml
> index 8ab43c8253b2..9918b9a84d88 100644
> --- a/Documentation/netlink/specs/nfsd.yaml
> +++ b/Documentation/netlink/specs/nfsd.yaml
> @@ -132,6 +132,15 @@ attribute-sets:
>        -
>          name: npools
>          type: u32
> +  -
> +    name: unlock-ip
> +    attributes:
> +      -
> +        name: address
> +        type: binary
> +        doc: struct sockaddr_in or struct sockaddr_in6.
> +        checks:
> +          min-len: 16
>  
>  operations:
>    list:
> @@ -233,3 +242,12 @@ operations:
>            attributes:
>              - mode
>              - npools
> +    -
> +      name: unlock-ip
> +      doc: release NLM locks held by an IP address
> +      attribute-set: unlock-ip
> +      flags: [admin-perm]
> +      do:
> +        request:
> +          attributes:
> +            - address
> diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c
> index 81c943345d13..6b7221ce6869 100644
> --- a/fs/nfsd/netlink.c
> +++ b/fs/nfsd/netlink.c
> @@ -48,6 +48,11 @@ static const struct nla_policy nfsd_pool_mode_set_nl_policy[NFSD_A_POOL_MODE_MOD
>  	[NFSD_A_POOL_MODE_MODE] = { .type = NLA_NUL_STRING, },
>  };
>  
> +/* NFSD_CMD_UNLOCK_IP - do */
> +static const struct nla_policy nfsd_unlock_ip_nl_policy[NFSD_A_UNLOCK_IP_ADDRESS + 1] = {
> +	[NFSD_A_UNLOCK_IP_ADDRESS] = NLA_POLICY_MIN_LEN(16),
> +};
> +
>  /* Ops table for nfsd */
>  static const struct genl_split_ops nfsd_nl_ops[] = {
>  	{
> @@ -103,6 +108,13 @@ static const struct genl_split_ops nfsd_nl_ops[] = {
>  		.doit	= nfsd_nl_pool_mode_get_doit,
>  		.flags	= GENL_CMD_CAP_DO,
>  	},
> +	{
> +		.cmd		= NFSD_CMD_UNLOCK_IP,
> +		.doit		= nfsd_nl_unlock_ip_doit,
> +		.policy		= nfsd_unlock_ip_nl_policy,
> +		.maxattr	= NFSD_A_UNLOCK_IP_ADDRESS,
> +		.flags		= GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
> +	},
>  };
>  
>  struct genl_family nfsd_nl_family __ro_after_init = {
> diff --git a/fs/nfsd/netlink.h b/fs/nfsd/netlink.h
> index 478117ff6b8c..3c2d5996612f 100644
> --- a/fs/nfsd/netlink.h
> +++ b/fs/nfsd/netlink.h
> @@ -26,6 +26,7 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info);
>  int nfsd_nl_listener_get_doit(struct sk_buff *skb, struct genl_info *info);
>  int nfsd_nl_pool_mode_set_doit(struct sk_buff *skb, struct genl_info *info);
>  int nfsd_nl_pool_mode_get_doit(struct sk_buff *skb, struct genl_info *info);
> +int nfsd_nl_unlock_ip_doit(struct sk_buff *skb, struct genl_info *info);
>  
>  extern struct genl_family nfsd_nl_family;
>  
> diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
> index 988a79ec4a79..e1e89d52e6de 100644
> --- a/fs/nfsd/nfsctl.c
> +++ b/fs/nfsd/nfsctl.c
> @@ -246,7 +246,7 @@ static ssize_t write_unlock_ip(struct file *file, char *buf, size_t size)
>  	if (rpc_pton(net, fo_path, size, sap, salen) == 0)
>  		return -EINVAL;
>  
> -	trace_nfsd_ctl_unlock_ip(net, buf);
> +	trace_nfsd_ctl_unlock_ip(net, sap, svc_addr_len(sap));
>  	return nlmsvc_unlock_all_by_ip(sap);
>  }
>  
> @@ -2200,6 +2200,44 @@ int nfsd_nl_pool_mode_get_doit(struct sk_buff *skb, struct genl_info *info)
>  	return err;
>  }
>  
> +/**
> + * nfsd_nl_unlock_ip_doit - release NLM locks held by an IP address
> + * @skb: reply buffer
> + * @info: netlink metadata and command arguments
> + *
> + * Return: 0 on success or a negative errno.
> + */
> +int nfsd_nl_unlock_ip_doit(struct sk_buff *skb, struct genl_info *info)
> +{
> +	struct sockaddr *sap;
> +
> +	if (GENL_REQ_ATTR_CHECK(info, NFSD_A_UNLOCK_IP_ADDRESS))
> +		return -EINVAL;
> +	sap = nla_data(info->attrs[NFSD_A_UNLOCK_IP_ADDRESS]);
> +	switch (sap->sa_family) {
> +	case AF_INET:
> +		if (nla_len(info->attrs[NFSD_A_UNLOCK_IP_ADDRESS]) <
> +		    sizeof(struct sockaddr_in))
> +			return -EINVAL;
> +		break;
> +	case AF_INET6:
> +		if (nla_len(info->attrs[NFSD_A_UNLOCK_IP_ADDRESS]) <
> +		    sizeof(struct sockaddr_in6))
> +			return -EINVAL;
> +		break;
> +	default:
> +		return -EAFNOSUPPORT;
> +	}
> +	/*
> +	 * nlmsvc_unlock_all_by_ip() releases matching locks
> +	 * across all network namespaces because lockd operates
> +	 * a single global instance.
> +	 */
> +	trace_nfsd_ctl_unlock_ip(genl_info_net(info), sap,
> +				 svc_addr_len(sap));

All of the tracepoints get passed svc_addr_len(sap) for the length. Any
reason not to just determine the length inside the tracepoint, so you
don't need to calc the length unless it's enabled?

> +	return nlmsvc_unlock_all_by_ip(sap);
> +}
> +
>  /**
>   * nfsd_net_init - Prepare the nfsd_net portion of a new net namespace
>   * @net: a freshly-created network namespace
> diff --git a/fs/nfsd/trace.h b/fs/nfsd/trace.h
> index 5ad38f50836d..976815f6f30f 100644
> --- a/fs/nfsd/trace.h
> +++ b/fs/nfsd/trace.h
> @@ -1985,19 +1985,20 @@ TRACE_EVENT(nfsd_cb_recall_any_done,
>  TRACE_EVENT(nfsd_ctl_unlock_ip,
>  	TP_PROTO(
>  		const struct net *net,
> -		const char *address
> +		const struct sockaddr *addr,
> +		const unsigned int addrlen
>  	),
> -	TP_ARGS(net, address),
> +	TP_ARGS(net, addr, addrlen),
>  	TP_STRUCT__entry(
>  		__field(unsigned int, netns_ino)
> -		__string(address, address)
> +		__sockaddr(addr, addrlen)
>  	),
>  	TP_fast_assign(
>  		__entry->netns_ino = net->ns.inum;
> -		__assign_str(address);
> +		__assign_sockaddr(addr, addr, addrlen);
>  	),
> -	TP_printk("address=%s",
> -		__get_str(address)
> +	TP_printk("addr=%pISpc",
> +		__get_sockaddr(addr)
>  	)
>  );
>  
> diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h
> index 97c7447f4d14..4153e9c69fbf 100644
> --- a/include/uapi/linux/nfsd_netlink.h
> +++ b/include/uapi/linux/nfsd_netlink.h
> @@ -81,6 +81,13 @@ enum {
>  	NFSD_A_POOL_MODE_MAX = (__NFSD_A_POOL_MODE_MAX - 1)
>  };
>  
> +enum {
> +	NFSD_A_UNLOCK_IP_ADDRESS = 1,
> +
> +	__NFSD_A_UNLOCK_IP_MAX,
> +	NFSD_A_UNLOCK_IP_MAX = (__NFSD_A_UNLOCK_IP_MAX - 1)
> +};
> +
>  enum {
>  	NFSD_CMD_RPC_STATUS_GET = 1,
>  	NFSD_CMD_THREADS_SET,
> @@ -91,6 +98,7 @@ enum {
>  	NFSD_CMD_LISTENER_GET,
>  	NFSD_CMD_POOL_MODE_SET,
>  	NFSD_CMD_POOL_MODE_GET,
> +	NFSD_CMD_UNLOCK_IP,
>  
>  	__NFSD_CMD_MAX,
>  	NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1)

-- 
Jeff Layton <jlayton@kernel.org>

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v5 0/7] Automatic NFSv4 state revocation on filesystem unmount
  2026-03-26 17:55 [PATCH v5 0/7] Automatic NFSv4 state revocation on filesystem unmount Chuck Lever
                   ` (7 preceding siblings ...)
  2026-03-27 12:03 ` [PATCH v5 0/7] Automatic NFSv4 state revocation on filesystem unmount Jeff Layton
@ 2026-03-27 12:18 ` Jeff Layton
  8 siblings, 0 replies; 22+ messages in thread
From: Jeff Layton @ 2026-03-27 12:18 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
  Cc: linux-nfs, linux-fsdevel, Chuck Lever

On Thu, 2026-03-26 at 13:55 -0400, Chuck Lever wrote:
> When an NFS server exports a filesystem and clients hold NFSv4
> state (opens, locks, delegations), unmounting the underlying
> filesystem fails with EBUSY. The /proc/fs/nfsd/unlock_ip and
> /proc/fs/nfsd/unlock_fs procfs interfaces handle this, but have
> no netlink equivalents, and unlock_fs operates at whole-superblock
> granularity.
> 
> This series adds three new NFSD netlink commands, each with its own
> attribute set:
> 
>  - NFSD_CMD_UNLOCK_IP releases NLM locks held by a client IP
>    address. Netlink equivalent of write_unlock_ip.
> 
>  - NFSD_CMD_UNLOCK_FILESYSTEM revokes all NFS state on a
>    superblock. Netlink equivalent of write_unlock_fs.
> 
>  - NFSD_CMD_UNLOCK_EXPORT revokes NFSv4 state acquired through
>    exports of a specific path, regardless of client.
> 
> UNLOCK_FILESYSTEM and UNLOCK_EXPORT serve different intents.
> UNLOCK_FILESYSTEM means "unmounting /data, release everything
> on this superblock." UNLOCK_EXPORT means "no clients remain for
> /data/projectA, release only the state acquired through exports
> of that path." Userspace (exportfs -u) sends UNLOCK_EXPORT after
> removing the last client for a given path, enabling the underlying
> filesystem to be unmounted.
> 
> The path-only design for UNLOCK_EXPORT avoids the auth_domain
> naming complexity (use_ipaddr vs hostname-based domains) by not
> requiring the caller to identify a specific client. Since this
> mechanism is to be used to enable umount, this seemed like a
> reasonable compromise.
> 
> ---
> Changes since v4:
> - 1/9 has been queued in nfsd-testing
> - Split single NFSD_CMD_UNLOCK into three separate commands
> - UNLOCK_EXPORT takes path only, no client attribute to avoid
>   auth_domain naming complexity with use_ipaddr
> 
> Changes since v3:
> - All VFS changes replaced with new netlink "unlock" operation
> 
> Changes since v2:
> - Replace fs_pin with an SRCU umount notifier chain in VFS
> - Merge the pending COPY cancellation patch
> - Replace xa_cmpxchg() with xa_insert()
> - Use cancel_work_sync() instead of flush_workqueue()
> - Remove rcu_barrier()
> - Correct misleading claims in kdoc comments and commit messages
> 
> Changes since v1:
> - Explain why drop_client() is being renamed
> - Finish implementing revocation on umount
> - Rename pin_insert_group
> - Clarified log output and code comments
> - Hold nfsd_mutex while closing nfsd_files
> 
> ---
> Chuck Lever (7):
>       NFSD: Extract revoke_one_stid() utility function
>       NFSD: Add NFSD_CMD_UNLOCK_IP netlink command
>       NFSD: Add NFSD_CMD_UNLOCK_FILESYSTEM netlink command
>       NFSD: Replace idr_for_each_entry_ul in find_one_sb_stid()
>       NFSD: Track svc_export in nfs4_stid
>       NFSD: Add NFSD_CMD_UNLOCK_EXPORT netlink command
>       NFSD: Close cached file handles when revoking export state
> 
>  Documentation/netlink/specs/nfsd.yaml |  61 +++++++++
>  fs/nfsd/filecache.c                   |  46 +++++++
>  fs/nfsd/filecache.h                   |   1 +
>  fs/nfsd/netlink.c                     |  36 ++++++
>  fs/nfsd/netlink.h                     |   3 +
>  fs/nfsd/nfs4layouts.c                 |   2 +
>  fs/nfsd/nfs4state.c                   | 226 +++++++++++++++++++++++-----------
>  fs/nfsd/nfsctl.c                      | 126 ++++++++++++++++++-
>  fs/nfsd/state.h                       |   6 +
>  fs/nfsd/trace.h                       |  32 ++++-
>  include/uapi/linux/nfsd_netlink.h     |  24 ++++
>  11 files changed, 484 insertions(+), 79 deletions(-)
> ---
> base-commit: 65058e9e9b20619f920397f529072e853dd43811
> change-id: 20260318-umount-kills-nfsv4-state-138218f2f4e0
> 
> Best regards,
> --  
> Chuck Lever

This all looks good to me. A couple of minor nits, but nothing that
looks wrong. I know you intend to use exportfs to drive the
UNLOCK_EXPORT. What are your plans for the userland bits to drive the
other two commands?

Reviewed-by: Jeff Layton <jlayton@kernel.org>

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v5 1/7] NFSD: Extract revoke_one_stid() utility function
  2026-03-26 17:55 ` [PATCH v5 1/7] NFSD: Extract revoke_one_stid() utility function Chuck Lever
                     ` (4 preceding siblings ...)
  2026-03-27 12:00   ` Jeff Layton
@ 2026-03-27 12:21   ` Jeff Layton
  2026-03-27 14:18     ` Chuck Lever
  5 siblings, 1 reply; 22+ messages in thread
From: Jeff Layton @ 2026-03-27 12:21 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
  Cc: linux-nfs, linux-fsdevel, Chuck Lever

On Thu, 2026-03-26 at 13:55 -0400, Chuck Lever wrote:
> From: Chuck Lever <chuck.lever@oracle.com>
> 
> The per-stateid revocation logic in nfsd4_revoke_states() handles
> four stateid types in a deeply nested switch. Extract two helpers:
> 
> revoke_ol_stid() performs admin-revocation of an open or lock
> stateid with st_mutex already held: marks the stateid as
> SC_STATUS_ADMIN_REVOKED, closes POSIX locks for lock stateids,
> and releases file access.
> 
> revoke_one_stid() dispatches by sc_type, acquires st_mutex with
> the appropriate lockdep class for open and lock stateids, and
> handles delegation unhash and layout close inline.
> 
> No functional change. Preparation for adding export-scoped state
> revocation which reuses revoke_one_stid().
> 
> Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
> ---
>  fs/nfsd/nfs4state.c | 137 ++++++++++++++++++++++++++--------------------------
>  1 file changed, 68 insertions(+), 69 deletions(-)
> 
> diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
> index 07df4511ba23..eb1bd1aae8f5 100644
> --- a/fs/nfsd/nfs4state.c
> +++ b/fs/nfsd/nfs4state.c
> @@ -1775,6 +1775,73 @@ static struct nfs4_stid *find_one_sb_stid(struct nfs4_client *clp,
>  	return stid;
>  }
>  
> +static void revoke_ol_stid(struct nfs4_client *clp,
> +			   struct nfs4_ol_stateid *stp)
> +{
> +	struct nfs4_stid *stid = &stp->st_stid;
> +
> +	lockdep_assert_held(&stp->st_mutex);
> +	spin_lock(&clp->cl_lock);
> +	if (stid->sc_status == 0) {
> +		stid->sc_status |= SC_STATUS_ADMIN_REVOKED;
> +		atomic_inc(&clp->cl_admin_revoked);
> +		spin_unlock(&clp->cl_lock);
> +		if (stid->sc_type == SC_TYPE_LOCK) {
> +			struct nfs4_lockowner *lo =
> +				lockowner(stp->st_stateowner);
> +			struct nfsd_file *nf;
> +
> +			nf = find_any_file(stp->st_stid.sc_file);
> +			if (nf) {
> +				get_file(nf->nf_file);
> +				filp_close(nf->nf_file, (fl_owner_t)lo);
> +				nfsd_file_put(nf);
> +			}
> +		}
> +		release_all_access(stp);
> +	} else
> +		spin_unlock(&clp->cl_lock);
> +}
> +
> +static void revoke_one_stid(struct nfs4_client *clp, struct nfs4_stid *stid)
> +{
> +	struct nfs4_ol_stateid *stp;
> +	struct nfs4_delegation *dp;
> +
> +	switch (stid->sc_type) {
> +	case SC_TYPE_OPEN:
> +		stp = openlockstateid(stid);
> +		mutex_lock_nested(&stp->st_mutex, OPEN_STATEID_MUTEX);
> +		revoke_ol_stid(clp, stp);
> +		mutex_unlock(&stp->st_mutex);
> +		break;
> +	case SC_TYPE_LOCK:
> +		stp = openlockstateid(stid);
> +		mutex_lock_nested(&stp->st_mutex, LOCK_STATEID_MUTEX);
> +		revoke_ol_stid(clp, stp);
> +		mutex_unlock(&stp->st_mutex);
> +		break;
> +	case SC_TYPE_DELEG:
> +		/*
> +		 * Extra reference guards against concurrent FREE_STATEID.
> +		 */
> +		refcount_inc(&stid->sc_count);
> +		dp = delegstateid(stid);
> +		spin_lock(&state_lock);

Oh hah, just noticed the kernel test robot pointed out the above.
Should be nn->deleg_lock now, no?

> +		if (!unhash_delegation_locked(dp, SC_STATUS_ADMIN_REVOKED))
> +			dp = NULL;
> +		spin_unlock(&state_lock);
> +		if (dp)
> +			revoke_delegation(dp);
> +		else
> +			nfs4_put_stid(stid);
> +		break;
> +	case SC_TYPE_LAYOUT:
> +		nfsd4_close_layout(layoutstateid(stid));
> +		break;
> +	}
> +}
> +
>  /**
>   * nfsd4_revoke_states - revoke all nfsv4 states associated with given filesystem
>   * @nn:   used to identify instance of nfsd (there is one per net namespace)
> @@ -1805,76 +1872,8 @@ void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb)
>  			struct nfs4_stid *stid = find_one_sb_stid(clp, sb,
>  								  sc_types);
>  			if (stid) {
> -				struct nfs4_ol_stateid *stp;
> -				struct nfs4_delegation *dp;
> -				struct nfs4_layout_stateid *ls;
> -
>  				spin_unlock(&nn->client_lock);
> -				switch (stid->sc_type) {
> -				case SC_TYPE_OPEN:
> -					stp = openlockstateid(stid);
> -					mutex_lock_nested(&stp->st_mutex,
> -							  OPEN_STATEID_MUTEX);
> -
> -					spin_lock(&clp->cl_lock);
> -					if (stid->sc_status == 0) {
> -						stid->sc_status |=
> -							SC_STATUS_ADMIN_REVOKED;
> -						atomic_inc(&clp->cl_admin_revoked);
> -						spin_unlock(&clp->cl_lock);
> -						release_all_access(stp);
> -					} else
> -						spin_unlock(&clp->cl_lock);
> -					mutex_unlock(&stp->st_mutex);
> -					break;
> -				case SC_TYPE_LOCK:
> -					stp = openlockstateid(stid);
> -					mutex_lock_nested(&stp->st_mutex,
> -							  LOCK_STATEID_MUTEX);
> -					spin_lock(&clp->cl_lock);
> -					if (stid->sc_status == 0) {
> -						struct nfs4_lockowner *lo =
> -							lockowner(stp->st_stateowner);
> -						struct nfsd_file *nf;
> -
> -						stid->sc_status |=
> -							SC_STATUS_ADMIN_REVOKED;
> -						atomic_inc(&clp->cl_admin_revoked);
> -						spin_unlock(&clp->cl_lock);
> -						nf = find_any_file(stp->st_stid.sc_file);
> -						if (nf) {
> -							get_file(nf->nf_file);
> -							filp_close(nf->nf_file,
> -								   (fl_owner_t)lo);
> -							nfsd_file_put(nf);
> -						}
> -						release_all_access(stp);
> -					} else
> -						spin_unlock(&clp->cl_lock);
> -					mutex_unlock(&stp->st_mutex);
> -					break;
> -				case SC_TYPE_DELEG:
> -					/* Extra reference guards against concurrent
> -					 * FREE_STATEID; revoke_delegation() consumes
> -					 * it, otherwise release it directly.
> -					 */
> -					refcount_inc(&stid->sc_count);
> -					dp = delegstateid(stid);
> -					spin_lock(&nn->deleg_lock);
> -					if (!unhash_delegation_locked(
> -						    dp, SC_STATUS_ADMIN_REVOKED))
> -						dp = NULL;
> -					spin_unlock(&nn->deleg_lock);
> -					if (dp)
> -						revoke_delegation(dp);
> -					else
> -						nfs4_put_stid(stid);
> -					break;
> -				case SC_TYPE_LAYOUT:
> -					ls = layoutstateid(stid);
> -					nfsd4_close_layout(ls);
> -					break;
> -				}
> +				revoke_one_stid(clp, stid);
>  				nfs4_put_stid(stid);
>  				spin_lock(&nn->client_lock);
>  				if (clp->cl_minorversion == 0)

-- 
Jeff Layton <jlayton@kernel.org>

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v5 0/7] Automatic NFSv4 state revocation on filesystem unmount
  2026-03-27 12:03 ` [PATCH v5 0/7] Automatic NFSv4 state revocation on filesystem unmount Jeff Layton
@ 2026-03-27 13:29   ` Chuck Lever
  0 siblings, 0 replies; 22+ messages in thread
From: Chuck Lever @ 2026-03-27 13:29 UTC (permalink / raw)
  To: Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
  Cc: linux-nfs, linux-fsdevel, Chuck Lever

On 3/27/26 8:03 AM, Jeff Layton wrote:
> On Thu, 2026-03-26 at 13:55 -0400, Chuck Lever wrote:
>> When an NFS server exports a filesystem and clients hold NFSv4
>> state (opens, locks, delegations), unmounting the underlying
>> filesystem fails with EBUSY. The /proc/fs/nfsd/unlock_ip and
>> /proc/fs/nfsd/unlock_fs procfs interfaces handle this, but have
>> no netlink equivalents, and unlock_fs operates at whole-superblock
>> granularity.
>>
>> This series adds three new NFSD netlink commands, each with its own
>> attribute set:
>>
>>  - NFSD_CMD_UNLOCK_IP releases NLM locks held by a client IP
>>    address. Netlink equivalent of write_unlock_ip.
>>
>>  - NFSD_CMD_UNLOCK_FILESYSTEM revokes all NFS state on a
>>    superblock. Netlink equivalent of write_unlock_fs.
>>
>>  - NFSD_CMD_UNLOCK_EXPORT revokes NFSv4 state acquired through
>>    exports of a specific path, regardless of client.
>>
>> UNLOCK_FILESYSTEM and UNLOCK_EXPORT serve different intents.
>> UNLOCK_FILESYSTEM means "unmounting /data, release everything
>> on this superblock." UNLOCK_EXPORT means "no clients remain for
>> /data/projectA, release only the state acquired through exports
>> of that path." Userspace (exportfs -u) sends UNLOCK_EXPORT after
>> removing the last client for a given path, enabling the underlying
>> filesystem to be unmounted.
>>
>> The path-only design for UNLOCK_EXPORT avoids the auth_domain
>> naming complexity (use_ipaddr vs hostname-based domains) by not
>> requiring the caller to identify a specific client. Since this
>> mechanism is to be used to enable umount, this seemed like a
>> reasonable compromise.
>>
> 
> Can you comment a bit about your intentions with userland here? Do you
> plan to extend nfsdctl to add this support or add a new binary to nfs-
> utils? Do you have userland patches or is that still TBD?

I have a version of exportfs that performs the netlink UNLOCK command as
part of unexporting. There are some conditions where it skips this; for
example, when unexporting all before a shutdown, otherwise state
recovery doesn't work.

These new UNLOCK commands stand alone as a feature, even though they are
not the full solution for addressing the unmounting failure after an
export, so I didn't post the exportfs patch this time. I'm sure there
are some details about that logic/policy that will need more debate.

Someone could add the UNLOCK commands to nfsdctl, but that isn't a
requirement for this work.


>> ---
>> Changes since v4:
>> - 1/9 has been queued in nfsd-testing
>> - Split single NFSD_CMD_UNLOCK into three separate commands
>> - UNLOCK_EXPORT takes path only, no client attribute to avoid
>>   auth_domain naming complexity with use_ipaddr
>>
>> Changes since v3:
>> - All VFS changes replaced with new netlink "unlock" operation
>>
>> Changes since v2:
>> - Replace fs_pin with an SRCU umount notifier chain in VFS
>> - Merge the pending COPY cancellation patch
>> - Replace xa_cmpxchg() with xa_insert()
>> - Use cancel_work_sync() instead of flush_workqueue()
>> - Remove rcu_barrier()
>> - Correct misleading claims in kdoc comments and commit messages
>>
>> Changes since v1:
>> - Explain why drop_client() is being renamed
>> - Finish implementing revocation on umount
>> - Rename pin_insert_group
>> - Clarified log output and code comments
>> - Hold nfsd_mutex while closing nfsd_files
>>
>> ---
>> Chuck Lever (7):
>>       NFSD: Extract revoke_one_stid() utility function
>>       NFSD: Add NFSD_CMD_UNLOCK_IP netlink command
>>       NFSD: Add NFSD_CMD_UNLOCK_FILESYSTEM netlink command
>>       NFSD: Replace idr_for_each_entry_ul in find_one_sb_stid()
>>       NFSD: Track svc_export in nfs4_stid
>>       NFSD: Add NFSD_CMD_UNLOCK_EXPORT netlink command
>>       NFSD: Close cached file handles when revoking export state
>>
>>  Documentation/netlink/specs/nfsd.yaml |  61 +++++++++
>>  fs/nfsd/filecache.c                   |  46 +++++++
>>  fs/nfsd/filecache.h                   |   1 +
>>  fs/nfsd/netlink.c                     |  36 ++++++
>>  fs/nfsd/netlink.h                     |   3 +
>>  fs/nfsd/nfs4layouts.c                 |   2 +
>>  fs/nfsd/nfs4state.c                   | 226 +++++++++++++++++++++++-----------
>>  fs/nfsd/nfsctl.c                      | 126 ++++++++++++++++++-
>>  fs/nfsd/state.h                       |   6 +
>>  fs/nfsd/trace.h                       |  32 ++++-
>>  include/uapi/linux/nfsd_netlink.h     |  24 ++++
>>  11 files changed, 484 insertions(+), 79 deletions(-)
>> ---
>> base-commit: 65058e9e9b20619f920397f529072e853dd43811
>> change-id: 20260318-umount-kills-nfsv4-state-138218f2f4e0
>>
>> Best regards,
>> --  
>> Chuck Lever
> 


-- 
Chuck Lever

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v5 1/7] NFSD: Extract revoke_one_stid() utility function
  2026-03-27 12:21   ` Jeff Layton
@ 2026-03-27 14:18     ` Chuck Lever
  0 siblings, 0 replies; 22+ messages in thread
From: Chuck Lever @ 2026-03-27 14:18 UTC (permalink / raw)
  To: Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
  Cc: linux-nfs, linux-fsdevel, Chuck Lever

On 3/27/26 8:21 AM, Jeff Layton wrote:
> On Thu, 2026-03-26 at 13:55 -0400, Chuck Lever wrote:
>> +	case SC_TYPE_DELEG:
>> +		/*
>> +		 * Extra reference guards against concurrent FREE_STATEID.
>> +		 */
>> +		refcount_inc(&stid->sc_count);
>> +		dp = delegstateid(stid);
>> +		spin_lock(&state_lock);
> 
> Oh hah, just noticed the kernel test robot pointed out the above.
> Should be nn->deleg_lock now, no?

Ja, I rebased just before sending these yesterday. Duh.


-- 
Chuck Lever

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v5 2/7] NFSD: Add NFSD_CMD_UNLOCK_IP netlink command
  2026-03-27 12:06   ` Jeff Layton
@ 2026-03-27 15:19     ` Chuck Lever
  2026-03-27 15:52       ` Jeff Layton
  0 siblings, 1 reply; 22+ messages in thread
From: Chuck Lever @ 2026-03-27 15:19 UTC (permalink / raw)
  To: Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
  Cc: linux-nfs, linux-fsdevel, Chuck Lever



On Fri, Mar 27, 2026, at 8:06 AM, Jeff Layton wrote:
> On Thu, 2026-03-26 at 13:55 -0400, Chuck Lever wrote:

>> diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
>> index 988a79ec4a79..e1e89d52e6de 100644
>> --- a/fs/nfsd/nfsctl.c
>> +++ b/fs/nfsd/nfsctl.c

>> @@ -2200,6 +2200,44 @@ int nfsd_nl_pool_mode_get_doit(struct sk_buff *skb, struct genl_info *info)
>>  	return err;
>>  }
>>  
>> +/**
>> + * nfsd_nl_unlock_ip_doit - release NLM locks held by an IP address
>> + * @skb: reply buffer
>> + * @info: netlink metadata and command arguments
>> + *
>> + * Return: 0 on success or a negative errno.
>> + */
>> +int nfsd_nl_unlock_ip_doit(struct sk_buff *skb, struct genl_info *info)
>> +{
>> +	struct sockaddr *sap;
>> +
>> +	if (GENL_REQ_ATTR_CHECK(info, NFSD_A_UNLOCK_IP_ADDRESS))
>> +		return -EINVAL;
>> +	sap = nla_data(info->attrs[NFSD_A_UNLOCK_IP_ADDRESS]);
>> +	switch (sap->sa_family) {
>> +	case AF_INET:
>> +		if (nla_len(info->attrs[NFSD_A_UNLOCK_IP_ADDRESS]) <
>> +		    sizeof(struct sockaddr_in))
>> +			return -EINVAL;
>> +		break;
>> +	case AF_INET6:
>> +		if (nla_len(info->attrs[NFSD_A_UNLOCK_IP_ADDRESS]) <
>> +		    sizeof(struct sockaddr_in6))
>> +			return -EINVAL;
>> +		break;
>> +	default:
>> +		return -EAFNOSUPPORT;
>> +	}
>> +	/*
>> +	 * nlmsvc_unlock_all_by_ip() releases matching locks
>> +	 * across all network namespaces because lockd operates
>> +	 * a single global instance.
>> +	 */
>> +	trace_nfsd_ctl_unlock_ip(genl_info_net(info), sap,
>> +				 svc_addr_len(sap));
>
> All of the tracepoints get passed svc_addr_len(sap) for the length. Any
> reason not to just determine the length inside the tracepoint, so you
> don't need to calc the length unless it's enabled?

Unless I'm mistaken, the trace_nfsd_ctl_unlock_ip() call site
expands to a static branch that skips everything, including
argument evaluation, when the tracepoint is disabled. The
svc_addr_len() call, being part of the argument list, is
already behind that branch.


>> +	return nlmsvc_unlock_all_by_ip(sap);
>> +}
>> +
>>  /**
>>   * nfsd_net_init - Prepare the nfsd_net portion of a new net namespace
>>   * @net: a freshly-created network namespace


-- 
Chuck Lever

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v5 2/7] NFSD: Add NFSD_CMD_UNLOCK_IP netlink command
  2026-03-27 15:19     ` Chuck Lever
@ 2026-03-27 15:52       ` Jeff Layton
  2026-03-27 16:02         ` Chuck Lever
  0 siblings, 1 reply; 22+ messages in thread
From: Jeff Layton @ 2026-03-27 15:52 UTC (permalink / raw)
  To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
  Cc: linux-nfs, linux-fsdevel, Chuck Lever

On Fri, 2026-03-27 at 11:19 -0400, Chuck Lever wrote:
> 
> On Fri, Mar 27, 2026, at 8:06 AM, Jeff Layton wrote:
> > On Thu, 2026-03-26 at 13:55 -0400, Chuck Lever wrote:
> 
> > > diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
> > > index 988a79ec4a79..e1e89d52e6de 100644
> > > --- a/fs/nfsd/nfsctl.c
> > > +++ b/fs/nfsd/nfsctl.c
> 
> > > @@ -2200,6 +2200,44 @@ int nfsd_nl_pool_mode_get_doit(struct sk_buff *skb, struct genl_info *info)
> > >  	return err;
> > >  }
> > >  
> > > +/**
> > > + * nfsd_nl_unlock_ip_doit - release NLM locks held by an IP address
> > > + * @skb: reply buffer
> > > + * @info: netlink metadata and command arguments
> > > + *
> > > + * Return: 0 on success or a negative errno.
> > > + */
> > > +int nfsd_nl_unlock_ip_doit(struct sk_buff *skb, struct genl_info *info)
> > > +{
> > > +	struct sockaddr *sap;
> > > +
> > > +	if (GENL_REQ_ATTR_CHECK(info, NFSD_A_UNLOCK_IP_ADDRESS))
> > > +		return -EINVAL;
> > > +	sap = nla_data(info->attrs[NFSD_A_UNLOCK_IP_ADDRESS]);
> > > +	switch (sap->sa_family) {
> > > +	case AF_INET:
> > > +		if (nla_len(info->attrs[NFSD_A_UNLOCK_IP_ADDRESS]) <
> > > +		    sizeof(struct sockaddr_in))
> > > +			return -EINVAL;
> > > +		break;
> > > +	case AF_INET6:
> > > +		if (nla_len(info->attrs[NFSD_A_UNLOCK_IP_ADDRESS]) <
> > > +		    sizeof(struct sockaddr_in6))
> > > +			return -EINVAL;
> > > +		break;
> > > +	default:
> > > +		return -EAFNOSUPPORT;
> > > +	}
> > > +	/*
> > > +	 * nlmsvc_unlock_all_by_ip() releases matching locks
> > > +	 * across all network namespaces because lockd operates
> > > +	 * a single global instance.
> > > +	 */
> > > +	trace_nfsd_ctl_unlock_ip(genl_info_net(info), sap,
> > > +				 svc_addr_len(sap));
> > 
> > All of the tracepoints get passed svc_addr_len(sap) for the length. Any
> > reason not to just determine the length inside the tracepoint, so you
> > don't need to calc the length unless it's enabled?
> 
> Unless I'm mistaken, the trace_nfsd_ctl_unlock_ip() call site
> expands to a static branch that skips everything, including
> argument evaluation, when the tracepoint is disabled. The
> svc_addr_len() call, being part of the argument list, is
> already behind that branch.
> 

It's a minor thing, but fewer arguments to a tracepoint is nicer, IMO.

> 
> > > +	return nlmsvc_unlock_all_by_ip(sap);
> > > +}
> > > +
> > >  /**
> > >   * nfsd_net_init - Prepare the nfsd_net portion of a new net namespace
> > >   * @net: a freshly-created network namespace
> 

-- 
Jeff Layton <jlayton@kernel.org>

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v5 2/7] NFSD: Add NFSD_CMD_UNLOCK_IP netlink command
  2026-03-27 15:52       ` Jeff Layton
@ 2026-03-27 16:02         ` Chuck Lever
  0 siblings, 0 replies; 22+ messages in thread
From: Chuck Lever @ 2026-03-27 16:02 UTC (permalink / raw)
  To: Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
  Cc: linux-nfs, linux-fsdevel, Chuck Lever



On Fri, Mar 27, 2026, at 11:52 AM, Jeff Layton wrote:
> On Fri, 2026-03-27 at 11:19 -0400, Chuck Lever wrote:
>> 
>> On Fri, Mar 27, 2026, at 8:06 AM, Jeff Layton wrote:
>> > On Thu, 2026-03-26 at 13:55 -0400, Chuck Lever wrote:
>> 
>> > > diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
>> > > index 988a79ec4a79..e1e89d52e6de 100644
>> > > --- a/fs/nfsd/nfsctl.c
>> > > +++ b/fs/nfsd/nfsctl.c
>> 
>> > > @@ -2200,6 +2200,44 @@ int nfsd_nl_pool_mode_get_doit(struct sk_buff *skb, struct genl_info *info)
>> > >  	return err;
>> > >  }
>> > >  
>> > > +/**
>> > > + * nfsd_nl_unlock_ip_doit - release NLM locks held by an IP address
>> > > + * @skb: reply buffer
>> > > + * @info: netlink metadata and command arguments
>> > > + *
>> > > + * Return: 0 on success or a negative errno.
>> > > + */
>> > > +int nfsd_nl_unlock_ip_doit(struct sk_buff *skb, struct genl_info *info)
>> > > +{
>> > > +	struct sockaddr *sap;
>> > > +
>> > > +	if (GENL_REQ_ATTR_CHECK(info, NFSD_A_UNLOCK_IP_ADDRESS))
>> > > +		return -EINVAL;
>> > > +	sap = nla_data(info->attrs[NFSD_A_UNLOCK_IP_ADDRESS]);
>> > > +	switch (sap->sa_family) {
>> > > +	case AF_INET:
>> > > +		if (nla_len(info->attrs[NFSD_A_UNLOCK_IP_ADDRESS]) <
>> > > +		    sizeof(struct sockaddr_in))
>> > > +			return -EINVAL;
>> > > +		break;
>> > > +	case AF_INET6:
>> > > +		if (nla_len(info->attrs[NFSD_A_UNLOCK_IP_ADDRESS]) <
>> > > +		    sizeof(struct sockaddr_in6))
>> > > +			return -EINVAL;
>> > > +		break;
>> > > +	default:
>> > > +		return -EAFNOSUPPORT;
>> > > +	}
>> > > +	/*
>> > > +	 * nlmsvc_unlock_all_by_ip() releases matching locks
>> > > +	 * across all network namespaces because lockd operates
>> > > +	 * a single global instance.
>> > > +	 */
>> > > +	trace_nfsd_ctl_unlock_ip(genl_info_net(info), sap,
>> > > +				 svc_addr_len(sap));
>> > 
>> > All of the tracepoints get passed svc_addr_len(sap) for the length. Any
>> > reason not to just determine the length inside the tracepoint, so you
>> > don't need to calc the length unless it's enabled?
>> 
>> Unless I'm mistaken, the trace_nfsd_ctl_unlock_ip() call site
>> expands to a static branch that skips everything, including
>> argument evaluation, when the tracepoint is disabled. The
>> svc_addr_len() call, being part of the argument list, is
>> already behind that branch.
>> 
>
> It's a minor thing, but fewer arguments to a tracepoint is nicer, IMO.

The TP_STRUCT__entry( ) macro needs the length value up front
to size the ring buffer entry. This is the cleanest way to do it.


>> 
>> > > +	return nlmsvc_unlock_all_by_ip(sap);
>> > > +}
>> > > +
>> > >  /**
>> > >   * nfsd_net_init - Prepare the nfsd_net portion of a new net namespace
>> > >   * @net: a freshly-created network namespace
>> 
>
> -- 
> Jeff Layton <jlayton@kernel.org>

-- 
Chuck Lever

^ permalink raw reply	[flat|nested] 22+ messages in thread

end of thread, other threads:[~2026-03-27 16:03 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-26 17:55 [PATCH v5 0/7] Automatic NFSv4 state revocation on filesystem unmount Chuck Lever
2026-03-26 17:55 ` [PATCH v5 1/7] NFSD: Extract revoke_one_stid() utility function Chuck Lever
2026-03-27  5:26   ` kernel test robot
2026-03-27  5:56   ` kernel test robot
2026-03-27 10:08   ` kernel test robot
2026-03-27 11:13   ` kernel test robot
2026-03-27 12:00   ` Jeff Layton
2026-03-27 12:21   ` Jeff Layton
2026-03-27 14:18     ` Chuck Lever
2026-03-26 17:55 ` [PATCH v5 2/7] NFSD: Add NFSD_CMD_UNLOCK_IP netlink command Chuck Lever
2026-03-27 12:06   ` Jeff Layton
2026-03-27 15:19     ` Chuck Lever
2026-03-27 15:52       ` Jeff Layton
2026-03-27 16:02         ` Chuck Lever
2026-03-26 17:55 ` [PATCH v5 3/7] NFSD: Add NFSD_CMD_UNLOCK_FILESYSTEM " Chuck Lever
2026-03-26 17:55 ` [PATCH v5 4/7] NFSD: Replace idr_for_each_entry_ul in find_one_sb_stid() Chuck Lever
2026-03-26 17:55 ` [PATCH v5 5/7] NFSD: Track svc_export in nfs4_stid Chuck Lever
2026-03-26 17:55 ` [PATCH v5 6/7] NFSD: Add NFSD_CMD_UNLOCK_EXPORT netlink command Chuck Lever
2026-03-26 17:55 ` [PATCH v5 7/7] NFSD: Close cached file handles when revoking export state Chuck Lever
2026-03-27 12:03 ` [PATCH v5 0/7] Automatic NFSv4 state revocation on filesystem unmount Jeff Layton
2026-03-27 13:29   ` Chuck Lever
2026-03-27 12:18 ` Jeff Layton

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox