* [PATCH v2 1/4] NFS: Add an outline for the CB_NOTIFY operation
2026-08-25 15:02 [PATCH v2 0/4] NFS: Add directory delegation support for CB_NOTIFY Anna Schumaker
@ 2026-08-25 15:02 ` Anna Schumaker
2026-08-25 15:02 ` [PATCH v2 2/4] NFS: Add support for CB_NOTIFY4_REMOVE_ENTRY Anna Schumaker
` (2 subsequent siblings)
3 siblings, 0 replies; 6+ messages in thread
From: Anna Schumaker @ 2026-08-25 15:02 UTC (permalink / raw)
To: linux-nfs, trond.myklebust; +Cc: anna
From: Anna Schumaker <anna.schumaker@oracle.com>
Support for individual notification types will be added over the next
few patches.
Signed-off-by: Anna Schumaker <anna.schumaker@oracle.com>
Signed-off-by: Anna Schumaker <anna.schumaker@hammerspace.com>
---
v2: Change res to a ssize_t
---
fs/nfs/callback.h | 14 +++++++++++
fs/nfs/callback_proc.c | 9 +++++++
fs/nfs/callback_xdr.c | 54 +++++++++++++++++++++++++++++++++++++++++-
fs/nfs/nfs4xdr.c | 2 +-
include/linux/nfs4.h | 3 +++
5 files changed, 80 insertions(+), 2 deletions(-)
diff --git a/fs/nfs/callback.h b/fs/nfs/callback.h
index 2a721c422d48..f7cc5b6931bf 100644
--- a/fs/nfs/callback.h
+++ b/fs/nfs/callback.h
@@ -143,6 +143,20 @@ struct cb_layoutrecallargs {
extern __be32 nfs4_callback_layoutrecall(void *argp, void *resp,
struct cb_process_state *cps);
+struct cb_notify_changes {
+ u32 notify_mask;
+};
+
+struct cb_notifyargs {
+ nfs4_stateid cna_stateid;
+ struct nfs_fh cna_fh;
+ uint32_t cna_n_changes;
+ struct cb_notify_changes *cna_changes;
+};
+
+extern __be32 nfs4_callback_notify(void *argp, void *resp,
+ struct cb_process_state *cps);
+
struct cb_devicenotifyitem {
uint32_t cbd_notify_type;
uint32_t cbd_layout_type;
diff --git a/fs/nfs/callback_proc.c b/fs/nfs/callback_proc.c
index 3fb10c8e4271..01600d75f2d9 100644
--- a/fs/nfs/callback_proc.c
+++ b/fs/nfs/callback_proc.c
@@ -369,6 +369,15 @@ static void pnfs_recall_all_layouts(struct nfs_client *clp,
do_callback_layoutrecall(clp, &args, cps);
}
+__be32 nfs4_callback_notify(void *argp, void *resp,
+ struct cb_process_state *cps)
+{
+ struct cb_notifyargs *args = argp;
+
+ kfree(args->cna_changes);
+ return 0;
+}
+
__be32 nfs4_callback_devicenotify(void *argp, void *resp,
struct cb_process_state *cps)
{
diff --git a/fs/nfs/callback_xdr.c b/fs/nfs/callback_xdr.c
index 4382baddc9ee..8d822a3ee248 100644
--- a/fs/nfs/callback_xdr.c
+++ b/fs/nfs/callback_xdr.c
@@ -31,6 +31,7 @@
#define CB_OP_RECALL_RES_MAXSZ (CB_OP_HDR_RES_MAXSZ)
#define CB_OP_LAYOUTRECALL_RES_MAXSZ (CB_OP_HDR_RES_MAXSZ)
+#define CB_OP_NOTIFY_RES_MAXSZ (CB_OP_HDR_RES_MAXSZ)
#define CB_OP_DEVICENOTIFY_RES_MAXSZ (CB_OP_HDR_RES_MAXSZ)
#define CB_OP_SEQUENCE_RES_MAXSZ (CB_OP_HDR_RES_MAXSZ + \
NFS4_MAX_SESSIONID_LEN + \
@@ -251,6 +252,52 @@ static __be32 decode_layoutrecall_args(struct svc_rqst *rqstp,
return 0;
}
+static
+__be32 decode_notify_args(struct svc_rqst *rqstp,
+ struct xdr_stream *xdr,
+ void *argp)
+{
+ struct cb_notifyargs *args = argp;
+ __be32 status;
+ ssize_t res;
+ __be32 *p;
+ int i;
+
+ status = decode_stateid(xdr, &args->cna_stateid);
+ if (unlikely(status != 0))
+ return status;
+
+ status = decode_fh(xdr, &args->cna_fh);
+ if (unlikely(status != 0))
+ return status;
+
+ p = xdr_inline_decode(xdr, 4);
+ if (unlikely(!p))
+ goto out;
+
+ args->cna_n_changes = ntohl(*p++);
+ args->cna_changes = kmalloc_array(args->cna_n_changes,
+ sizeof(*args->cna_changes),
+ GFP_KERNEL);
+ if (!args->cna_changes)
+ goto out;
+
+ for (i = 0; i < args->cna_n_changes; i++) {
+ struct cb_notify_changes *change = &args->cna_changes[i];
+
+ res = xdr_stream_decode_uint32_array(xdr,
+ &change->notify_mask, 1);
+ if (unlikely(res < 0))
+ goto err;
+ }
+
+ return 0;
+err:
+ kfree(args->cna_changes);
+out:
+ return htonl(NFS4ERR_BADXDR);
+}
+
static
__be32 decode_devicenotify_args(struct svc_rqst *rqstp,
struct xdr_stream *xdr,
@@ -797,12 +844,12 @@ preprocess_nfs41_op(int nop, unsigned int op_nr, struct callback_op **op)
case OP_CB_RECALL_ANY:
case OP_CB_RECALL_SLOT:
case OP_CB_LAYOUTRECALL:
+ case OP_CB_NOTIFY:
case OP_CB_NOTIFY_DEVICEID:
case OP_CB_NOTIFY_LOCK:
*op = &callback_ops[op_nr];
break;
- case OP_CB_NOTIFY:
case OP_CB_PUSH_DELEG:
case OP_CB_RECALLABLE_OBJ_AVAIL:
case OP_CB_WANTS_CANCELLED:
@@ -1034,6 +1081,11 @@ static struct callback_op callback_ops[] = {
.decode_args = decode_layoutrecall_args,
.res_maxsize = CB_OP_LAYOUTRECALL_RES_MAXSZ,
},
+ [OP_CB_NOTIFY] = {
+ .process_op = nfs4_callback_notify,
+ .decode_args = decode_notify_args,
+ .res_maxsize = CB_OP_NOTIFY_RES_MAXSZ,
+ },
[OP_CB_NOTIFY_DEVICEID] = {
.process_op = nfs4_callback_devicenotify,
.decode_args = decode_devicenotify_args,
diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
index fc049ce4ba8a..5c51691fff03 100644
--- a/fs/nfs/nfs4xdr.c
+++ b/fs/nfs/nfs4xdr.c
@@ -2012,7 +2012,7 @@ static void
encode_get_dir_delegation(struct xdr_stream *xdr, struct compound_hdr *hdr)
{
struct timespec64 ts = { 0, 0 };
- u32 notifications[1] = { 0 };
+ u32 notifications[1] = { CB_NOTIFY4_GFLAG_EXTEND };
u32 attributes[1] = { 0 };
__be32 *p;
diff --git a/include/linux/nfs4.h b/include/linux/nfs4.h
index 1a3981c26b23..2e33f2ffdc31 100644
--- a/include/linux/nfs4.h
+++ b/include/linux/nfs4.h
@@ -491,6 +491,9 @@ enum {
#define THRESHOLD_RD_IO (1UL << 2)
#define THRESHOLD_WR_IO (1UL << 3)
+/* Directory Delegation / CB_NOTIFY bits */
+#define CB_NOTIFY4_GFLAG_EXTEND (1UL << 6)
+
#define NFSPROC4_NULL 0
#define NFSPROC4_COMPOUND 1
#define NFS4_VERSION 4
--
2.55.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v2 2/4] NFS: Add support for CB_NOTIFY4_REMOVE_ENTRY
2026-08-25 15:02 [PATCH v2 0/4] NFS: Add directory delegation support for CB_NOTIFY Anna Schumaker
2026-08-25 15:02 ` [PATCH v2 1/4] NFS: Add an outline for the CB_NOTIFY operation Anna Schumaker
@ 2026-08-25 15:02 ` Anna Schumaker
2026-08-25 16:11 ` Jeff Layton
2026-08-25 15:02 ` [PATCH v2 3/4] NFS: Add support for CB_NOTIFY4_ADD_ENTRY Anna Schumaker
2026-08-25 15:02 ` [PATCH v2 4/4] NFS: Add support for CB_NOTIFY4_RENAME_ENTRY Anna Schumaker
3 siblings, 1 reply; 6+ messages in thread
From: Anna Schumaker @ 2026-08-25 15:02 UTC (permalink / raw)
To: linux-nfs, trond.myklebust; +Cc: anna
From: Anna Schumaker <anna.schumaker@oracle.com>
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 <anna.schumaker@oracle.com>
Signed-off-by: Anna Schumaker <anna.schumaker@hammerspace.com>
---
v2:
* Initialize 'res' to 0 in nfs4_callback_notify()
* Add missing error handling
* Remove nfs4_cb_notify_lookup()
---
fs/nfs/callback.h | 16 ++++++++++-
fs/nfs/callback_proc.c | 63 +++++++++++++++++++++++++++++++++++++++++-
fs/nfs/callback_xdr.c | 62 +++++++++++++++++++++++++++++++++++++++++
fs/nfs/nfs4xdr.c | 3 +-
include/linux/nfs4.h | 1 +
5 files changed, 142 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 01600d75f2d9..d8ad3cd792df 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>
@@ -369,13 +370,73 @@ static void pnfs_recall_all_layouts(struct nfs_client *clp,
do_callback_layoutrecall(clp, &args, cps);
}
+static __be32 nfs4_cb_notify_remove(struct cb_process_state *cps,
+ struct dentry *parent,
+ struct cb_notify_remove *cb_remove)
+{
+ struct qstr filename = QSTR_INIT(cb_remove->nrm_old_entry.ne_name,
+ cb_remove->nrm_old_entry.ne_namelen);
+ struct dentry *child;
+
+ filename.hash = full_name_hash(parent, filename.name, filename.len);
+ child = d_lookup(parent, &filename);
+ if (IS_ERR_OR_NULL(child))
+ return htonl(NFS4ERR_BADHANDLE);
+
+ d_drop(child);
+ dput(child);
+
+ nfs_set_cache_invalid(parent->d_inode, NFS_INO_INVALID_DATA);
+ 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 = 0;
+ 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)
+ 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 (unlikely(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 8d822a3ee248..612d215ac66e 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,24 @@ __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;
+ }
+
+ if (unlikely(status != 0))
+ goto err;
}
return 0;
diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
index 5c51691fff03..e275fbfd395e 100644
--- a/fs/nfs/nfs4xdr.c
+++ b/fs/nfs/nfs4xdr.c
@@ -2012,7 +2012,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 2e33f2ffdc31..8de3ea283767 100644
--- a/include/linux/nfs4.h
+++ b/include/linux/nfs4.h
@@ -492,6 +492,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
^ permalink raw reply related [flat|nested] 6+ messages in thread* Re: [PATCH v2 2/4] NFS: Add support for CB_NOTIFY4_REMOVE_ENTRY
2026-08-25 15:02 ` [PATCH v2 2/4] NFS: Add support for CB_NOTIFY4_REMOVE_ENTRY Anna Schumaker
@ 2026-08-25 16:11 ` Jeff Layton
0 siblings, 0 replies; 6+ messages in thread
From: Jeff Layton @ 2026-08-25 16:11 UTC (permalink / raw)
To: Anna Schumaker, linux-nfs, trond.myklebust
On Tue, 2026-08-25 at 11:02 -0400, Anna Schumaker wrote:
> From: Anna Schumaker <anna.schumaker@oracle.com>
>
> 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 <anna.schumaker@oracle.com>
> Signed-off-by: Anna Schumaker <anna.schumaker@hammerspace.com>
> ---
> v2:
> * Initialize 'res' to 0 in nfs4_callback_notify()
> * Add missing error handling
> * Remove nfs4_cb_notify_lookup()
> ---
> fs/nfs/callback.h | 16 ++++++++++-
> fs/nfs/callback_proc.c | 63 +++++++++++++++++++++++++++++++++++++++++-
> fs/nfs/callback_xdr.c | 62 +++++++++++++++++++++++++++++++++++++++++
> fs/nfs/nfs4xdr.c | 3 +-
> include/linux/nfs4.h | 1 +
> 5 files changed, 142 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 01600d75f2d9..d8ad3cd792df 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>
> @@ -369,13 +370,73 @@ static void pnfs_recall_all_layouts(struct nfs_client *clp,
> do_callback_layoutrecall(clp, &args, cps);
> }
>
> +static __be32 nfs4_cb_notify_remove(struct cb_process_state *cps,
> + struct dentry *parent,
> + struct cb_notify_remove *cb_remove)
> +{
> + struct qstr filename = QSTR_INIT(cb_remove->nrm_old_entry.ne_name,
> + cb_remove->nrm_old_entry.ne_namelen);
> + struct dentry *child;
> +
> + filename.hash = full_name_hash(parent, filename.name, filename.len);
> + child = d_lookup(parent, &filename);
> + if (IS_ERR_OR_NULL(child))
> + return htonl(NFS4ERR_BADHANDLE);
> +
> + d_drop(child);
Should this instead make this a negative dentry?
I think this will just drop the existing dentry and it'll trigger a
lookup next time we end up trying to hit it.
> + dput(child);
> +
> + nfs_set_cache_invalid(parent->d_inode, NFS_INO_INVALID_DATA);
> + 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 = 0;
>
> + 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)
> + 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 (unlikely(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 8d822a3ee248..612d215ac66e 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,24 @@ __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;
> + }
> +
> + if (unlikely(status != 0))
> + goto err;
> }
>
> return 0;
> diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
> index 5c51691fff03..e275fbfd395e 100644
> --- a/fs/nfs/nfs4xdr.c
> +++ b/fs/nfs/nfs4xdr.c
> @@ -2012,7 +2012,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 2e33f2ffdc31..8de3ea283767 100644
> --- a/include/linux/nfs4.h
> +++ b/include/linux/nfs4.h
> @@ -492,6 +492,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
--
Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v2 3/4] NFS: Add support for CB_NOTIFY4_ADD_ENTRY
2026-08-25 15:02 [PATCH v2 0/4] NFS: Add directory delegation support for CB_NOTIFY Anna Schumaker
2026-08-25 15:02 ` [PATCH v2 1/4] NFS: Add an outline for the CB_NOTIFY operation Anna Schumaker
2026-08-25 15:02 ` [PATCH v2 2/4] NFS: Add support for CB_NOTIFY4_REMOVE_ENTRY Anna Schumaker
@ 2026-08-25 15:02 ` Anna Schumaker
2026-08-25 15:02 ` [PATCH v2 4/4] NFS: Add support for CB_NOTIFY4_RENAME_ENTRY Anna Schumaker
3 siblings, 0 replies; 6+ messages in thread
From: Anna Schumaker @ 2026-08-25 15:02 UTC (permalink / raw)
To: linux-nfs, trond.myklebust; +Cc: anna
From: Anna Schumaker <anna.schumaker@oracle.com>
When the server tells us that a directory entry has been created, we
need to instantiate a new dentry and add it to the dcache on the client.
Signed-off-by: Anna Schumaker <anna.schumaker@oracle.com>
Signed-off-by: Anna Schumaker <anna.schumaker@hammerspace.com>
---
v2:
* Handle the na_have_old_entry case
* Initialize fattr->valid to 0 before decoding
* Properly check the return code from decode_fattr_cb()
* Rewrite nfs4_cb_notify_add() to closer resemble nfs_prime_dcache()
---
fs/nfs/callback.h | 17 +++++++++++
fs/nfs/callback_proc.c | 61 +++++++++++++++++++++++++++++++++++++
fs/nfs/callback_xdr.c | 69 ++++++++++++++++++++++++++++++++++--------
fs/nfs/nfs4_fs.h | 2 ++
fs/nfs/nfs4xdr.c | 29 ++++++++++++++++--
include/linux/nfs4.h | 1 +
6 files changed, 164 insertions(+), 15 deletions(-)
diff --git a/fs/nfs/callback.h b/fs/nfs/callback.h
index 3740c999bb82..d3cc3e5df776 100644
--- a/fs/nfs/callback.h
+++ b/fs/nfs/callback.h
@@ -147,6 +147,12 @@ struct cb_notify_entry {
u32 ne_namelen;
const char *ne_name;
struct nfs_fattr ne_attrs;
+ struct nfs_fh ne_fh;
+};
+
+struct cb_notify_prev_entry {
+ struct cb_notify_entry pe_prev_entry;
+ u64 pe_prev_entry_cookie;
};
struct cb_notify_remove {
@@ -154,10 +160,21 @@ struct cb_notify_remove {
u64 nrm_old_entry_cookie;
};
+struct cb_notify_add {
+ bool na_have_old_entry;
+ struct cb_notify_remove na_old_entry;
+ struct cb_notify_entry na_new_entry;
+ bool na_have_new_entry_cookie;
+ u64 na_new_entry_cookie;
+ bool na_have_prev_entry;
+ bool na_last_entry;
+};
+
struct cb_notify_changes {
u32 notify_mask;
union {
struct cb_notify_remove notify_remove;
+ struct cb_notify_add notify_add;
};
};
diff --git a/fs/nfs/callback_proc.c b/fs/nfs/callback_proc.c
index d8ad3cd792df..9ac786f258c2 100644
--- a/fs/nfs/callback_proc.c
+++ b/fs/nfs/callback_proc.c
@@ -390,6 +390,63 @@ static __be32 nfs4_cb_notify_remove(struct cb_process_state *cps,
return 0;
}
+static __be32 nfs4_cb_notify_add(struct cb_process_state *cps,
+ struct dentry *parent,
+ struct cb_notify_add *cb_add)
+{
+ struct nfs_entry entry = {
+ .cookie = cb_add->na_new_entry_cookie,
+ .name = cb_add->na_new_entry.ne_name,
+ .len = cb_add->na_new_entry.ne_namelen,
+ .eof = cb_add->na_last_entry,
+ .fh = &cb_add->na_new_entry.ne_fh,
+ .fattr = &cb_add->na_new_entry.ne_attrs,
+ .server = NFS_SERVER(d_inode(parent)),
+ };
+ struct qstr filename = QSTR_INIT(entry.name, entry.len);
+ struct dentry *dentry, *alias;
+ struct inode *inode;
+
+ if (cb_add->na_have_old_entry)
+ nfs4_cb_notify_remove(cps, parent, &cb_add->na_old_entry);
+
+ filename.hash = full_name_hash(parent, filename.name, filename.len);
+ dentry = d_lookup(parent, &filename);
+ if (!dentry) {
+ dentry = d_alloc_parallel(parent, &filename);
+ if (IS_ERR(dentry))
+ goto out;
+ }
+
+ if (!d_in_lookup(dentry)) {
+ nfs_set_verifier(dentry, parent->d_time);
+ if (!nfs_refresh_inode(d_inode(dentry), entry.fattr))
+ nfs_setsecurity(d_inode(dentry), entry.fattr);
+ goto out;
+ }
+
+ if (!entry.fh->size) {
+ d_lookup_done(dentry);
+ goto out;
+ }
+
+ nfs_set_verifier(dentry, parent->d_time);
+ inode = nfs_fhget(parent->d_sb, entry.fh, entry.fattr);
+ alias = d_splice_alias(inode, dentry);
+ d_lookup_done(dentry);
+
+ if (IS_ERR_OR_NULL(alias))
+ goto out;
+ nfs_set_verifier(alias, parent->d_time);
+ dput(dentry);
+ dentry = alias;
+
+out:
+ nfs_set_cache_invalid(parent->d_inode, NFS_INO_INVALID_DATA);
+ dput(dentry);
+ return 0;
+}
+
__be32 nfs4_callback_notify(void *argp, void *resp,
struct cb_process_state *cps)
{
@@ -421,6 +478,10 @@ __be32 nfs4_callback_notify(void *argp, void *resp,
res = nfs4_cb_notify_remove(cps, parent,
&change->notify_remove);
break;
+ case CB_NOTIFY4_ADD_ENTRY:
+ res = nfs4_cb_notify_add(cps, parent,
+ &change->notify_add);
+ break;
default:
res = htonl(NFS4ERR_NOTSUPP);
goto out_dput;
diff --git a/fs/nfs/callback_xdr.c b/fs/nfs/callback_xdr.c
index 612d215ac66e..39a879e82312 100644
--- a/fs/nfs/callback_xdr.c
+++ b/fs/nfs/callback_xdr.c
@@ -257,26 +257,20 @@ static __be32 decode_notify_entry(struct xdr_stream *xdr,
{
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);
+ status = xdr_stream_decode_uint32_array(xdr, bitmap, 3);
+ if (unlikely(status == 0 || status > 3))
+ return htonl(NFS4ERR_BADXDR);
+
+ status = decode_fattr_cb(xdr, bitmap, &args->ne_attrs, &args->ne_fh);
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;
+ return status;
}
static __be32 decode_notify_remove(struct xdr_stream *xdr,
@@ -296,6 +290,54 @@ static __be32 decode_notify_remove(struct xdr_stream *xdr,
return 0;
}
+static __be32 decode_notify_add(struct xdr_stream *xdr,
+ struct cb_notify_add *args)
+{
+ __be32 status;
+ __be32 *p;
+
+ p = xdr_inline_decode(xdr, 4);
+ if (unlikely(!p))
+ return htonl(NFS4ERR_BADXDR);
+ args->na_have_old_entry = ntohl(*p);
+
+ if (args->na_have_old_entry) {
+ status = decode_notify_remove(xdr, &args->na_old_entry);
+ if (unlikely(status != 0))
+ return status;
+ }
+
+ status = decode_notify_entry(xdr, &args->na_new_entry);
+ if (unlikely(status != 0))
+ return status;
+
+ p = xdr_inline_decode(xdr, 4);
+ if (unlikely(!p))
+ return htonl(NFS4ERR_BADXDR);
+ args->na_have_new_entry_cookie = ntohl(*p);
+
+ if (args->na_have_new_entry_cookie) {
+ p = xdr_inline_decode(xdr, 8);
+ if (unlikely(!p))
+ return htonl(NFS4ERR_BADXDR);
+
+ xdr_decode_hyper(p, &args->na_new_entry_cookie);
+ }
+
+ p = xdr_inline_decode(xdr, 4);
+ if (unlikely(!p))
+ return htonl(NFS4ERR_BADXDR);
+ args->na_have_prev_entry = ntohl(*p);
+
+ WARN_ONCE(args->na_have_prev_entry, "NFS: Have prev entry unimplemented\n");
+
+ p = xdr_inline_decode(xdr, 4);
+ if (unlikely(!p))
+ return htonl(NFS4ERR_BADXDR);
+ args->na_last_entry = ntohl(*p);
+ return 0;
+}
+
static
__be32 decode_notify_args(struct svc_rqst *rqstp,
struct xdr_stream *xdr,
@@ -345,6 +387,9 @@ __be32 decode_notify_args(struct svc_rqst *rqstp,
status = decode_notify_remove(xdr,
&change->notify_remove);
break;
+ case CB_NOTIFY4_ADD_ENTRY:
+ status = decode_notify_add(xdr, &change->notify_add);
+ break;
default:
goto err;
}
diff --git a/fs/nfs/nfs4_fs.h b/fs/nfs/nfs4_fs.h
index b48e5b87cb2a..b029c9cefc38 100644
--- a/fs/nfs/nfs4_fs.h
+++ b/fs/nfs/nfs4_fs.h
@@ -586,6 +586,8 @@ extern const u32 nfs42_maxlistxattrs_overhead;
#endif
struct nfs4_mount_data;
+int decode_fattr_cb(struct xdr_stream *xdr, uint32_t *bitmap,
+ struct nfs_fattr *fattr, struct nfs_fh *fhandle);
/* callback_xdr.c */
extern const struct svc_version nfs4_callback_version1;
diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
index e275fbfd395e..9a7c4d23aea7 100644
--- a/fs/nfs/nfs4xdr.c
+++ b/fs/nfs/nfs4xdr.c
@@ -2013,8 +2013,11 @@ encode_get_dir_delegation(struct xdr_stream *xdr, struct compound_hdr *hdr)
{
struct timespec64 ts = { 0, 0 };
u32 notifications[1] = { CB_NOTIFY4_REMOVE_ENTRY |
+ CB_NOTIFY4_ADD_ENTRY |
CB_NOTIFY4_GFLAG_EXTEND };
- u32 attributes[1] = { 0 };
+ u32 child_attrs[1] = { FATTR4_WORD0_FSID | FATTR4_WORD0_FILEID |
+ FATTR4_WORD0_TYPE | FATTR4_WORD0_FILEHANDLE };
+ u32 dir_attrs[1] = { 0 };
__be32 *p;
encode_op_hdr(xdr, OP_GET_DIR_DELEGATION, decode_get_dir_deleg_maxsz, hdr);
@@ -2030,10 +2033,10 @@ encode_get_dir_delegation(struct xdr_stream *xdr, struct compound_hdr *hdr)
xdr_encode_nfstime4(p, &ts);
/* Requested child attributes */
- xdr_encode_bitmap4(xdr, attributes, ARRAY_SIZE(attributes));
+ xdr_encode_bitmap4(xdr, child_attrs, ARRAY_SIZE(child_attrs));
/* Requested dir attributes */
- xdr_encode_bitmap4(xdr, attributes, ARRAY_SIZE(attributes));
+ xdr_encode_bitmap4(xdr, dir_attrs, ARRAY_SIZE(dir_attrs));
}
static void
@@ -4919,6 +4922,26 @@ static int decode_getfattr(struct xdr_stream *xdr, struct nfs_fattr *fattr,
return decode_getfattr_generic(xdr, fattr, NULL, NULL, server);
}
+int decode_fattr_cb(struct xdr_stream *xdr, uint32_t *bitmap,
+ struct nfs_fattr *fattr, struct nfs_fh *fhandle)
+{
+ unsigned int savep;
+ uint32_t attrlen;
+ int status;
+
+ fattr->valid = 0;
+
+ status = decode_attr_length(xdr, &attrlen, &savep);
+ if (status < 0)
+ return status;
+
+ status = decode_getfattr_attrs(xdr, bitmap, fattr, fhandle, NULL, NULL);
+ if (status < 0)
+ return status;
+
+ return verify_attr_len(xdr, savep, attrlen);
+}
+
/*
* Decode potentially multiple layout types.
*/
diff --git a/include/linux/nfs4.h b/include/linux/nfs4.h
index 8de3ea283767..4b507a93aa38 100644
--- a/include/linux/nfs4.h
+++ b/include/linux/nfs4.h
@@ -493,6 +493,7 @@ enum {
/* Directory Delegation / CB_NOTIFY bits */
#define CB_NOTIFY4_REMOVE_ENTRY (1UL << 2)
+#define CB_NOTIFY4_ADD_ENTRY (1UL << 3)
#define CB_NOTIFY4_GFLAG_EXTEND (1UL << 6)
#define NFSPROC4_NULL 0
--
2.55.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v2 4/4] NFS: Add support for CB_NOTIFY4_RENAME_ENTRY
2026-08-25 15:02 [PATCH v2 0/4] NFS: Add directory delegation support for CB_NOTIFY Anna Schumaker
` (2 preceding siblings ...)
2026-08-25 15:02 ` [PATCH v2 3/4] NFS: Add support for CB_NOTIFY4_ADD_ENTRY Anna Schumaker
@ 2026-08-25 15:02 ` Anna Schumaker
3 siblings, 0 replies; 6+ messages in thread
From: Anna Schumaker @ 2026-08-25 15:02 UTC (permalink / raw)
To: linux-nfs, trond.myklebust; +Cc: anna
From: Anna Schumaker <anna.schumaker@oracle.com>
Renaming an entry essentially combines a remove with an add, allowing us
to reuse most of the code from the previous patches to do the work for
us.
Signed-off-by: Anna Schumaker <anna.schumaker@oracle.com>
Signed-off-by: Anna Schumaker <anna.schumaker@hammerspace.com>
---
fs/nfs/callback.h | 6 ++++++
fs/nfs/callback_proc.c | 16 ++++++++++++++++
fs/nfs/callback_xdr.c | 14 ++++++++++++++
fs/nfs/nfs4xdr.c | 1 +
include/linux/nfs4.h | 1 +
5 files changed, 38 insertions(+)
diff --git a/fs/nfs/callback.h b/fs/nfs/callback.h
index d3cc3e5df776..019ee9eed9fe 100644
--- a/fs/nfs/callback.h
+++ b/fs/nfs/callback.h
@@ -170,11 +170,17 @@ struct cb_notify_add {
bool na_last_entry;
};
+struct cb_notify_rename {
+ struct cb_notify_remove nrn_old_entry;
+ struct cb_notify_add nrn_new_entry;
+};
+
struct cb_notify_changes {
u32 notify_mask;
union {
struct cb_notify_remove notify_remove;
struct cb_notify_add notify_add;
+ struct cb_notify_rename notify_rename;
};
};
diff --git a/fs/nfs/callback_proc.c b/fs/nfs/callback_proc.c
index 9ac786f258c2..2e533c8e297a 100644
--- a/fs/nfs/callback_proc.c
+++ b/fs/nfs/callback_proc.c
@@ -447,6 +447,18 @@ static __be32 nfs4_cb_notify_add(struct cb_process_state *cps,
return 0;
}
+static __be32 nfs4_cb_notify_rename(struct cb_process_state *cps,
+ struct dentry *parent,
+ struct cb_notify_rename *cb_rename)
+{
+ __be32 status;
+
+ status = nfs4_cb_notify_remove(cps, parent, &cb_rename->nrn_old_entry);
+ if (status != 0)
+ return status;
+ return nfs4_cb_notify_add(cps, parent, &cb_rename->nrn_new_entry);
+}
+
__be32 nfs4_callback_notify(void *argp, void *resp,
struct cb_process_state *cps)
{
@@ -482,6 +494,10 @@ __be32 nfs4_callback_notify(void *argp, void *resp,
res = nfs4_cb_notify_add(cps, parent,
&change->notify_add);
break;
+ case CB_NOTIFY4_RENAME_ENTRY:
+ res = nfs4_cb_notify_rename(cps, parent,
+ &change->notify_rename);
+ break;
default:
res = htonl(NFS4ERR_NOTSUPP);
goto out_dput;
diff --git a/fs/nfs/callback_xdr.c b/fs/nfs/callback_xdr.c
index 39a879e82312..e787657c5db0 100644
--- a/fs/nfs/callback_xdr.c
+++ b/fs/nfs/callback_xdr.c
@@ -338,6 +338,17 @@ static __be32 decode_notify_add(struct xdr_stream *xdr,
return 0;
}
+static __be32 decode_notify_rename(struct xdr_stream *xdr,
+ struct cb_notify_rename *args)
+{
+ __be32 status;
+
+ status = decode_notify_remove(xdr, &args->nrn_old_entry);
+ if (unlikely(status != 0))
+ return status;
+ return decode_notify_add(xdr, &args->nrn_new_entry);
+}
+
static
__be32 decode_notify_args(struct svc_rqst *rqstp,
struct xdr_stream *xdr,
@@ -390,6 +401,9 @@ __be32 decode_notify_args(struct svc_rqst *rqstp,
case CB_NOTIFY4_ADD_ENTRY:
status = decode_notify_add(xdr, &change->notify_add);
break;
+ case CB_NOTIFY4_RENAME_ENTRY:
+ status = decode_notify_rename(xdr, &change->notify_rename);
+ break;
default:
goto err;
}
diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
index 9a7c4d23aea7..6d102ebf5e6d 100644
--- a/fs/nfs/nfs4xdr.c
+++ b/fs/nfs/nfs4xdr.c
@@ -2014,6 +2014,7 @@ encode_get_dir_delegation(struct xdr_stream *xdr, struct compound_hdr *hdr)
struct timespec64 ts = { 0, 0 };
u32 notifications[1] = { CB_NOTIFY4_REMOVE_ENTRY |
CB_NOTIFY4_ADD_ENTRY |
+ CB_NOTIFY4_RENAME_ENTRY |
CB_NOTIFY4_GFLAG_EXTEND };
u32 child_attrs[1] = { FATTR4_WORD0_FSID | FATTR4_WORD0_FILEID |
FATTR4_WORD0_TYPE | FATTR4_WORD0_FILEHANDLE };
diff --git a/include/linux/nfs4.h b/include/linux/nfs4.h
index 4b507a93aa38..27f083546421 100644
--- a/include/linux/nfs4.h
+++ b/include/linux/nfs4.h
@@ -494,6 +494,7 @@ enum {
/* Directory Delegation / CB_NOTIFY bits */
#define CB_NOTIFY4_REMOVE_ENTRY (1UL << 2)
#define CB_NOTIFY4_ADD_ENTRY (1UL << 3)
+#define CB_NOTIFY4_RENAME_ENTRY (1UL << 4)
#define CB_NOTIFY4_GFLAG_EXTEND (1UL << 6)
#define NFSPROC4_NULL 0
--
2.55.0
^ permalink raw reply related [flat|nested] 6+ messages in thread