[PATCH 2/4] NFS: Add support for CB_NOTIFY4_REMOVE_ENTRY

Anna Schumaker <[email protected]> Tue, 4 Aug 2026 16:59:53 -0400
Newsgroups gmane.linux.nfs
Message-ID <[email protected]>
From: Anna Schumaker <[email protected]>

When the server tells us that a directory entry has been removed then we
need to take that as an indication that our knowledge of the directory
has changed and needs to be refreshed.

Signed-off-by: Anna Schumaker <[email protected]>
Signed-off-by: Anna Schumaker <anna.schumaker-F/[email protected]>
---
 fs/nfs/callback.h      | 16 +++++++++-
 fs/nfs/callback_proc.c | 68 +++++++++++++++++++++++++++++++++++++++++-
 fs/nfs/callback_xdr.c  | 59 ++++++++++++++++++++++++++++++++++++
 fs/nfs/nfs4xdr.c       |  3 +-
 include/linux/nfs4.h   |  1 +
 5 files changed, 144 insertions(+), 3 deletions(-)

diff --git a/fs/nfs/callback.h b/fs/nfs/callback.h
index f7cc5b6931bf..3740c999bb82 100644
--- a/fs/nfs/callback.h
+++ b/fs/nfs/callback.h
@@ -143,8 +143,22 @@ struct cb_layoutrecallargs {
 extern __be32 nfs4_callback_layoutrecall(void *argp, void *resp,
 		struct cb_process_state *cps);
 
+struct cb_notify_entry {
+	u32			ne_namelen;
+	const char		*ne_name;
+	struct nfs_fattr	ne_attrs;
+};
+
+struct cb_notify_remove {
+	struct cb_notify_entry	nrm_old_entry;
+	u64			nrm_old_entry_cookie;
+};
+
 struct cb_notify_changes {
-	u32 notify_mask;
+	u32	notify_mask;
+	union {
+		struct cb_notify_remove notify_remove;
+	};
 };
 
 struct cb_notifyargs {
diff --git a/fs/nfs/callback_proc.c b/fs/nfs/callback_proc.c
index 5c414b3b01d8..356fbd8428ec 100644
--- a/fs/nfs/callback_proc.c
+++ b/fs/nfs/callback_proc.c
@@ -9,6 +9,7 @@
 
 #include <linux/errno.h>
 #include <linux/math.h>
+#include <linux/namei.h>
 #include <linux/nfs4.h>
 #include <linux/nfs_fs.h>
 #include <linux/slab.h>
@@ -368,13 +369,78 @@ static void pnfs_recall_all_layouts(struct nfs_client *clp,
 	do_callback_layoutrecall(clp, &args, cps);
 }
 
+static struct dentry *nfs4_cb_notify_lookup(struct dentry *parent,
+					    struct cb_notify_entry *entry)
+{
+	struct qstr filename = QSTR_INIT(entry->ne_name, entry->ne_namelen);
+	return try_lookup_noperm(&filename, parent);
+}
+
+static __be32 nfs4_cb_notify_remove(struct cb_process_state *cps,
+				    struct dentry *parent,
+				    struct cb_notify_remove *cb_remove)
+{
+	struct dentry *child;
+
+	child = nfs4_cb_notify_lookup(parent, &cb_remove->nrm_old_entry);
+	if (IS_ERR_OR_NULL(child))
+		return htonl(NFS4ERR_BADHANDLE);
+
+	nfs_set_cache_invalid(parent->d_inode, NFS_INO_INVALID_DATA);
+	d_drop(child);
+	dput(child);
+	return 0;
+}
+
 __be32 nfs4_callback_notify(void *argp, void *resp,
 			    struct cb_process_state *cps)
 {
 	struct cb_notifyargs *args = argp;
+	struct dentry *parent;
+	struct inode *inode;
+	unsigned int i;
+	__be32 res;
 
+	if (!cps->clp) {
+		res = htonl(NFS4ERR_OP_NOT_IN_SESSION);
+		goto out;
+	}
+
+	inode = nfs_delegation_find_inode(cps->clp, &args->cna_fh);
+	if (IS_ERR(inode)) {
+		res = htonl(NFS4ERR_BADHANDLE);
+		goto out;
+	}
+	parent = d_find_alias(inode);
+	if (!parent) {
+		res = 0;
+		goto out_iput;
+	}
+
+	for (i = 0; i < args->cna_n_changes; i++) {
+		struct cb_notify_changes *change = &args->cna_changes[i];
+
+		switch (change->notify_mask) {
+		case CB_NOTIFY4_REMOVE_ENTRY:
+			res = nfs4_cb_notify_remove(cps, parent,
+						    &change->notify_remove);
+			break;
+		default:
+			res = htonl(NFS4ERR_NOTSUPP);
+			goto out_dput;
+		}
+
+		if (res < 0)
+			break;
+	}
+
+out_dput:
+	dput(parent);
+out_iput:
+	nfs_iput_and_deactive(inode);
+out:
 	kfree(args->cna_changes);
-	return 0;
+	return res;
 }
 
 __be32 nfs4_callback_devicenotify(void *argp, void *resp,
diff --git a/fs/nfs/callback_xdr.c b/fs/nfs/callback_xdr.c
index b96d0028fa3d..fd54f31e89ac 100644
--- a/fs/nfs/callback_xdr.c
+++ b/fs/nfs/callback_xdr.c
@@ -252,6 +252,50 @@ static __be32 decode_layoutrecall_args(struct svc_rqst *rqstp,
 	return 0;
 }
 
+static __be32 decode_notify_entry(struct xdr_stream *xdr,
+				  struct cb_notify_entry *args)
+{
+	uint32_t bitmap[3] = {0};
+	__be32 status;
+	u32 attrlen;
+	__be32 *p;
+
+	status = decode_string(xdr, &args->ne_namelen, &args->ne_name,
+				NFS4_OPAQUE_LIMIT);
+	if (unlikely(status != 0))
+		return status;
+
+	status = decode_bitmap(xdr, bitmap);
+	if (unlikely(status != 0))
+		return status;
+
+	p = xdr_inline_decode(xdr, 4);
+	if (unlikely(!p))
+		return htonl(NFS4ERR_BADXDR);
+
+	attrlen = be32_to_cpup(p);
+	if (attrlen != 0)
+		return htonl(NFS4ERR_BADXDR);
+	return 0;
+}
+
+static __be32 decode_notify_remove(struct xdr_stream *xdr,
+				   struct cb_notify_remove *args)
+{
+	__be32 status;
+	__be32 *p;
+
+	status = decode_notify_entry(xdr, &args->nrm_old_entry);
+	if (unlikely(status != 0))
+		return status;
+
+	p = xdr_inline_decode(xdr, 8);
+	if (unlikely(!p))
+		return htonl(NFS4ERR_BADXDR);
+	xdr_decode_hyper(p, &args->nrm_old_entry_cookie);
+	return 0;
+}
+
 static
 __be32 decode_notify_args(struct svc_rqst *rqstp,
 			  struct xdr_stream *xdr,
@@ -289,6 +333,21 @@ __be32 decode_notify_args(struct svc_rqst *rqstp,
 						     &change->notify_mask, 1);
 		if (unlikely(res < 0))
 			goto err;
+
+		/* Decode opaque size */
+		p = xdr_inline_decode(xdr, 4);
+		if (unlikely(!p))
+			goto err;
+		res = ntohl(*p);
+
+		switch (change->notify_mask) {
+		case CB_NOTIFY4_REMOVE_ENTRY:
+			status = decode_notify_remove(xdr,
+						      &change->notify_remove);
+			break;
+		default:
+			goto err;
+		}
 	}
 
 	return 0;
diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
index 64b8fd061852..65f9e995fa62 100644
--- a/fs/nfs/nfs4xdr.c
+++ b/fs/nfs/nfs4xdr.c
@@ -2011,7 +2011,8 @@ static void
 encode_get_dir_delegation(struct xdr_stream *xdr, struct compound_hdr *hdr)
 {
 	struct timespec64 ts = { 0, 0 };
-	u32 notifications[1] = { CB_NOTIFY4_GFLAG_EXTEND };
+	u32 notifications[1] = { CB_NOTIFY4_REMOVE_ENTRY |
+				 CB_NOTIFY4_GFLAG_EXTEND };
 	u32 attributes[1] = { 0 };
 	__be32 *p;
 
diff --git a/include/linux/nfs4.h b/include/linux/nfs4.h
index fcf0eee55c35..0f84be8e2fd8 100644
--- a/include/linux/nfs4.h
+++ b/include/linux/nfs4.h
@@ -483,6 +483,7 @@ enum {
 #define THRESHOLD_WR_IO                 (1UL << 3)
 
 /* Directory Delegation / CB_NOTIFY bits */
+#define CB_NOTIFY4_REMOVE_ENTRY		(1UL << 2)
 #define CB_NOTIFY4_GFLAG_EXTEND		(1UL << 6)
 
 #define NFSPROC4_NULL 0
-- 
2.55.0