* [PATCH pynfs v2 01/25] nfs4.1: add proposed NOTIFY4_GFLAG_EXTEND flag
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 02/25] nfs4.1: add a getfh() to the end of create_obj() compound Jeff Layton
` (23 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
This flag has been proposed as part of RFC8881bis. This flag is used to
negotiate extensions to the original directory delegations originally
specified in RFC8881.
In practice, the Linux nfs server requires that the client support this
flag if it's requesting anything other than a recall-only delegation.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/xdrdef/nfs4.x | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/nfs4.1/xdrdef/nfs4.x b/nfs4.1/xdrdef/nfs4.x
index ee3da8aa7a34..f03eb538a298 100644
--- a/nfs4.1/xdrdef/nfs4.x
+++ b/nfs4.1/xdrdef/nfs4.x
@@ -3611,7 +3611,8 @@ enum notify_type4 {
NOTIFY4_REMOVE_ENTRY = 2,
NOTIFY4_ADD_ENTRY = 3,
NOTIFY4_RENAME_ENTRY = 4,
- NOTIFY4_CHANGE_COOKIE_VERIFIER = 5
+ NOTIFY4_CHANGE_COOKIE_VERIFIER = 5,
+ NOTIFY4_GFLAG_EXTEND = 6 /* proposed in rfc8881bis */
};
/* Changed entry information. */
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v2 02/25] nfs4.1: add a getfh() to the end of create_obj() compound
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 01/25] nfs4.1: add proposed NOTIFY4_GFLAG_EXTEND flag Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 03/25] server41tests: add a basic GET_DIR_DELEGATION test Jeff Layton
` (22 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
A later patch will use the create_obj() function to create files, but
some of the tests require us to know the filehandle of the created
object. Add a GETFH operation to the end of the create_obj() compound.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/environment.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/nfs4.1/server41tests/environment.py b/nfs4.1/server41tests/environment.py
index 3c77153631ae..f5b1fea4a64c 100644
--- a/nfs4.1/server41tests/environment.py
+++ b/nfs4.1/server41tests/environment.py
@@ -474,7 +474,7 @@ def create_obj(sess, path, kind=NF4DIR, attrs={FATTR4_MODE:0o755}):
# Ensure using createtype4
if not hasattr(kind, "type"):
kind = createtype4(kind)
- ops = use_obj(path[:-1]) + [op.create(kind, path[-1], attrs)]
+ ops = use_obj(path[:-1]) + [op.create(kind, path[-1], attrs), op.getfh()]
return sess.compound(ops)
def open_create_file(sess, owner, path=None, attrs={FATTR4_MODE: 0o644},
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v2 03/25] server41tests: add a basic GET_DIR_DELEGATION test
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 01/25] nfs4.1: add proposed NOTIFY4_GFLAG_EXTEND flag Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 02/25] nfs4.1: add a getfh() to the end of create_obj() compound Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 04/25] server41tests: add a test for duplicate GET_DIR_DELEGATION requests Jeff Layton
` (21 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
Test basic dir delegation handout, recall and return
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/nfs4client.py | 6 +++
nfs4.1/server41tests/__init__.py | 1 +
nfs4.1/server41tests/st_dir_deleg.py | 102 +++++++++++++++++++++++++++++++++++
3 files changed, 109 insertions(+)
diff --git a/nfs4.1/nfs4client.py b/nfs4.1/nfs4client.py
index 25d7fd16f12b..dbe7af761f16 100644
--- a/nfs4.1/nfs4client.py
+++ b/nfs4.1/nfs4client.py
@@ -283,6 +283,12 @@ class NFS4Client(rpc.Client, rpc.Server):
res = self.posthook(arg, env, res=NFS4_OK)
return encode_status(res)
+ def op_cb_notify(self, arg, env):
+ log_cb.info("In CB_NOTIFY")
+ self.prehook(arg, env)
+ res = self.posthook(arg, env, res=NFS4_OK)
+ return encode_status(res)
+
def op_cb_notify_lock(self, arg, env):
log_cb.info("In CB_NOTIFY_LOCK")
self.prehook(arg, env)
diff --git a/nfs4.1/server41tests/__init__.py b/nfs4.1/server41tests/__init__.py
index 156c5e3082eb..c654212c617e 100644
--- a/nfs4.1/server41tests/__init__.py
+++ b/nfs4.1/server41tests/__init__.py
@@ -28,4 +28,5 @@ __all__ = ["st_exchange_id.py", # draft 21
"st_xattr.py",
"st_courtesy.py",
"st_callback.py",
+ "st_dir_deleg.py",
]
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
new file mode 100644
index 000000000000..ab4387f0bd9b
--- /dev/null
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -0,0 +1,102 @@
+from .st_create_session import create_session
+from .st_open import open_claim4
+from xdrdef.nfs4_const import *
+from xdrdef.nfs4_pack import NFS4Unpacker
+
+from .environment import check, fail, create_obj, use_obj
+from xdrdef.nfs4_type import *
+import nfs_ops
+op = nfs_ops.NFS4ops()
+import nfs4lib
+import threading
+
+zerotime = nfstime4(seconds=0, nseconds=0)
+
+def decode_notify_event(change):
+ """Decode a notify4 into its typed event structure."""
+ mask = nfs4lib.bitmap2list(change.notify_mask)
+ unpacker = NFS4Unpacker(change.notify_vals)
+ if NOTIFY4_REMOVE_ENTRY in mask:
+ return (NOTIFY4_REMOVE_ENTRY, unpacker.unpack_notify_remove4())
+ elif NOTIFY4_ADD_ENTRY in mask:
+ return (NOTIFY4_ADD_ENTRY, unpacker.unpack_notify_add4())
+ elif NOTIFY4_RENAME_ENTRY in mask:
+ return (NOTIFY4_RENAME_ENTRY, unpacker.unpack_notify_rename4())
+ elif NOTIFY4_CHANGE_DIR_ATTRS in mask:
+ return (NOTIFY4_CHANGE_DIR_ATTRS, unpacker.unpack_notify_attr4())
+ return (None, None)
+
+def bitmap4_to_int(bitmap):
+ """Convert a bitmap4 (list of uint32 words) to a single integer."""
+ result = 0
+ for i, word in enumerate(bitmap):
+ result |= word << (32 * i)
+ return result
+
+def _getDirDeleg(t, env, notify_mask, cb):
+ def recall_pre_hook(arg, env):
+ cb.stateid = arg.stateid # NOTE this must be done before set()
+ cb.cred = env.cred.raw_cred
+ cb.got_recall = True
+ env.notify = cb.set # This is called after compound sent to queue
+ def recall_post_hook(arg, env, res):
+ return res
+ def notify_pre_hook(arg, env):
+ cb.stateid = arg.cna_stateid
+ cb.fh = arg.cna_fh
+ cb.changes = arg.cna_changes
+ cb.got_notify = True
+ env.notify = cb.set # This is called after compound sent to queue
+ def notify_post_hook(arg, env, res):
+ return res
+
+ cb.got_recall = False
+ cb.got_notify = False
+
+ c = env.c1
+ sess1 = c.new_client_session(b"%s_1" % env.testname(t))
+ sess1.client.cb_pre_hook(OP_CB_RECALL, recall_pre_hook)
+ sess1.client.cb_post_hook(OP_CB_RECALL, recall_post_hook)
+ sess1.client.cb_pre_hook(OP_CB_NOTIFY, notify_pre_hook)
+ sess1.client.cb_post_hook(OP_CB_NOTIFY, notify_post_hook)
+
+ topdir = c.homedir + [t.code.encode('utf8')]
+ res = create_obj(sess1, topdir)
+ check(res)
+ fh = res.resarray[-1].object
+
+ ops = [ op.putfh(fh), op.get_dir_delegation(False,
+ nfs4lib.list2bitmap(notify_mask),
+ zerotime, zerotime,
+ nfs4lib.list2bitmap([]),
+ nfs4lib.list2bitmap([]))]
+ res = sess1.compound(ops)
+ check(res)
+ deleg = res.resarray[-1].gddrnf_resok4.gddr_stateid
+ return (sess1, fh, deleg)
+
+def testDirDelegSimple(t, env):
+ """Test basic dir delegation handout, recall and return
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG1
+ """
+ c = env.c1
+ recall = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env, [], recall)
+
+ # new client -- create a file in the dir
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim) ]
+ slot = sess2.compound_async(open_op)
+ completed = recall.wait(2)
+ env.sleep(.1)
+
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+ check(res)
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v2 04/25] server41tests: add a test for duplicate GET_DIR_DELEGATION requests
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
` (2 preceding siblings ...)
2026-04-16 18:14 ` [PATCH pynfs v2 03/25] server41tests: add a basic GET_DIR_DELEGATION test Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 05/25] server41tests: pass_warn() when server doesn't support dir delegations Jeff Layton
` (20 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
---
nfs4.1/server41tests/st_dir_deleg.py | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index ab4387f0bd9b..e062cbe27bff 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -100,3 +100,29 @@ def testDirDelegSimple(t, env):
ops = [ op.putfh(fh), op.delegreturn(deleg) ]
res = sess1.compound(ops)
check(res)
+
+def testDirDelegDuplicate(t, env):
+ """Test that server returns GDD4_UNAVAIL on duplicate GDD4 request
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG2
+ """
+ c = env.c1
+ recall = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env, [], recall)
+
+ # get a dir deleg with no notifications
+ ops = [ op.putfh(fh), op.get_dir_delegation(False,
+ nfs4lib.list2bitmap([]),
+ zerotime, zerotime,
+ nfs4lib.list2bitmap([]),
+ nfs4lib.list2bitmap([]))]
+ res = sess1.compound(ops)
+ check(res)
+ nfstatus = res.resarray[-1].gddr_res_non_fatal4.gddrnf_status
+ if (nfstatus != GDD4_UNAVAIL):
+ fail("Server replied to duplicate request with %d" % nfstatus)
+
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+ check(res)
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v2 05/25] server41tests: pass_warn() when server doesn't support dir delegations
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
` (3 preceding siblings ...)
2026-04-16 18:14 ` [PATCH pynfs v2 04/25] server41tests: add a test for duplicate GET_DIR_DELEGATION requests Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 06/25] server41tests: test remove triggers dir delegation recall Jeff Layton
` (19 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
Instead of just failing the test when GET_DIR_DELEGATION isn't supported
or the server doesn't hand out a directory delegation, have it pass with
a warning instead. This should make it safe to keep the directory
delegation tests in the "all" group.
Also, when receiving a directory delegation, vet that it got the
requested notifications. Just pass_warn() if it didn't.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 21 ++++++++++++++++++---
1 file changed, 18 insertions(+), 3 deletions(-)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index e062cbe27bff..e5de20c52ba4 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -65,14 +65,29 @@ def _getDirDeleg(t, env, notify_mask, cb):
check(res)
fh = res.resarray[-1].object
- ops = [ op.putfh(fh), op.get_dir_delegation(False,
- nfs4lib.list2bitmap(notify_mask),
+ mask_bm = nfs4lib.list2bitmap(notify_mask)
+ ops = [ op.putfh(fh), op.get_dir_delegation(False, nfs4lib.list2bitmap(notify_mask),
zerotime, zerotime,
nfs4lib.list2bitmap([]),
nfs4lib.list2bitmap([]))]
res = sess1.compound(ops)
- check(res)
+ check(res, [NFS4_OK, NFS4ERR_NOTSUPP])
+ if (res.status == NFS4ERR_NOTSUPP):
+ t.pass_warn("Server doesn't support GET_DIR_DELEGATION")
+
+ nf = res.resarray[-1].gddr_res_non_fatal4
+ if nf.gddrnf_status == GDD4_UNAVAIL:
+ t.pass_warn("Server reported that delegation on new dir was unavailable.")
+ elif nf.gddrnf_status != GDD4_OK:
+ t.fail("Server returned unknown non-fatal status code.")
+
deleg = res.resarray[-1].gddrnf_resok4.gddr_stateid
+ if NOTIFY4_GFLAG_EXTEND in notify_mask and \
+ nf.gddrnf_resok4.gddr_notification != mask_bm:
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+ t.pass_warn("Server didn't offer the necessary directory notifications for this test")
+
return (sess1, fh, deleg)
def testDirDelegSimple(t, env):
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v2 06/25] server41tests: test remove triggers dir delegation recall
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
` (4 preceding siblings ...)
2026-04-16 18:14 ` [PATCH pynfs v2 05/25] server41tests: pass_warn() when server doesn't support dir delegations Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 07/25] server41tests: test rename " Jeff Layton
` (18 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
Get a dir delegation with no notification mask, create a file, then
remove it from a second client. Verify that the server issues a
CB_RECALL.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 36 +++++++++++++++++++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index e5de20c52ba4..f5265e8cf0ab 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -3,7 +3,7 @@ from .st_open import open_claim4
from xdrdef.nfs4_const import *
from xdrdef.nfs4_pack import NFS4Unpacker
-from .environment import check, fail, create_obj, use_obj
+from .environment import check, fail, create_obj, use_obj, close_file, rename_obj
from xdrdef.nfs4_type import *
import nfs_ops
op = nfs_ops.NFS4ops()
@@ -141,3 +141,37 @@ def testDirDelegDuplicate(t, env):
ops = [ op.putfh(fh), op.delegreturn(deleg) ]
res = sess1.compound(ops)
check(res)
+
+def testDirDelegRemoveRecall(t, env):
+ """Verify remove triggers dir delegation recall
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG3
+ """
+ c = env.c1
+ recall = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env, [], recall)
+
+ # Create a file from sess1
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess1.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+ close_file(sess1, file_fh, stateid=open_stateid)
+
+ # Remove the file from sess2 -- should trigger recall
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ remove_op = [ op.putfh(fh), op.remove(env.testname(t)) ]
+ slot = sess2.compound_async(remove_op)
+ completed = recall.wait(2)
+ env.sleep(.1)
+
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+ check(res)
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v2 07/25] server41tests: test rename triggers dir delegation recall
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
` (5 preceding siblings ...)
2026-04-16 18:14 ` [PATCH pynfs v2 06/25] server41tests: test remove triggers dir delegation recall Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-17 0:40 ` Scott Mayhew
2026-04-16 18:14 ` [PATCH pynfs v2 08/25] server41tests: test mkdir " Jeff Layton
` (17 subsequent siblings)
24 siblings, 1 reply; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
Get a dir delegation with no notification mask, create a file, then
rename it from a second client. Verify that the server issues a
CB_RECALL.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 33 +++++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index f5265e8cf0ab..d8d09cd4bf7e 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -175,3 +175,36 @@ def testDirDelegRemoveRecall(t, env):
ops = [ op.putfh(fh), op.delegreturn(deleg) ]
res = sess1.compound(ops)
check(res)
+
+def testDirDelegRenameRecall(t, env):
+ """Verify rename triggers dir delegation recall
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG4
+ """
+ c = env.c1
+ recall = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env, [], recall)
+
+ # Create a file from sess1
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim) ]
+ res = sess1.compound(open_op)
+ check(res)
+
+ # Rename the file from sess2 -- should trigger recall
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ rename_op = [ op.putfh(fh), op.savefh(),
+ op.putfh(fh),
+ op.rename(env.testname(t), b"%s_2" % env.testname(t)) ]
+ slot = sess2.compound_async(rename_op)
+ completed = recall.wait(2)
+ env.sleep(.1)
+
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+ check(res)
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH pynfs v2 07/25] server41tests: test rename triggers dir delegation recall
2026-04-16 18:14 ` [PATCH pynfs v2 07/25] server41tests: test rename " Jeff Layton
@ 2026-04-17 0:40 ` Scott Mayhew
2026-04-17 15:42 ` Jeff Layton
0 siblings, 1 reply; 28+ messages in thread
From: Scott Mayhew @ 2026-04-17 0:40 UTC (permalink / raw)
To: Jeff Layton
Cc: Calum Mackay, Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo,
Tom Talpey, Trond Myklebust, Anna Schumaker, linux-nfs
On Thu, 16 Apr 2026, Jeff Layton wrote:
> Get a dir delegation with no notification mask, create a file, then
> rename it from a second client. Verify that the server issues a
> CB_RECALL.
>
> Signed-off-by: Jeff Layton <jlayton@kernel.org>
> ---
> nfs4.1/server41tests/st_dir_deleg.py | 33 +++++++++++++++++++++++++++++++++
> 1 file changed, 33 insertions(+)
>
> diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
> index f5265e8cf0ab..d8d09cd4bf7e 100644
> --- a/nfs4.1/server41tests/st_dir_deleg.py
> +++ b/nfs4.1/server41tests/st_dir_deleg.py
> @@ -175,3 +175,36 @@ def testDirDelegRemoveRecall(t, env):
> ops = [ op.putfh(fh), op.delegreturn(deleg) ]
> res = sess1.compound(ops)
> check(res)
> +
> +def testDirDelegRenameRecall(t, env):
> + """Verify rename triggers dir delegation recall
> +
> + FLAGS: dirdeleg all
> + CODE: DIRDELEG4
> + """
> + c = env.c1
> + recall = threading.Event()
> + sess1, fh, deleg = _getDirDeleg(t, env, [], recall)
> +
> + # Create a file from sess1
> + claim = open_claim4(CLAIM_NULL, env.testname(t))
> + owner = open_owner4(0, b"owner")
> + how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
> + open_op = [ op.putfh(fh), op.open(0,
> + OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
> + OPEN4_SHARE_DENY_NONE, owner, how, claim) ]
> + res = sess1.compound(open_op)
> + check(res)
You probably want to close this file at the end of the test so you're
not leaving state behind on the server.
-Scott
> +
> + # Rename the file from sess2 -- should trigger recall
> + sess2 = c.new_client_session(b"%s_2" % env.testname(t))
> + rename_op = [ op.putfh(fh), op.savefh(),
> + op.putfh(fh),
> + op.rename(env.testname(t), b"%s_2" % env.testname(t)) ]
> + slot = sess2.compound_async(rename_op)
> + completed = recall.wait(2)
> + env.sleep(.1)
> +
> + ops = [ op.putfh(fh), op.delegreturn(deleg) ]
> + res = sess1.compound(ops)
> + check(res)
>
> --
> 2.53.0
>
>
^ permalink raw reply [flat|nested] 28+ messages in thread* Re: [PATCH pynfs v2 07/25] server41tests: test rename triggers dir delegation recall
2026-04-17 0:40 ` Scott Mayhew
@ 2026-04-17 15:42 ` Jeff Layton
0 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-17 15:42 UTC (permalink / raw)
To: Scott Mayhew
Cc: Calum Mackay, Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo,
Tom Talpey, Trond Myklebust, Anna Schumaker, linux-nfs
On Thu, 2026-04-16 at 20:40 -0400, Scott Mayhew wrote:
> On Thu, 16 Apr 2026, Jeff Layton wrote:
>
> > Get a dir delegation with no notification mask, create a file, then
> > rename it from a second client. Verify that the server issues a
> > CB_RECALL.
> >
> > Signed-off-by: Jeff Layton <jlayton@kernel.org>
> > ---
> > nfs4.1/server41tests/st_dir_deleg.py | 33 +++++++++++++++++++++++++++++++++
> > 1 file changed, 33 insertions(+)
> >
> > diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
> > index f5265e8cf0ab..d8d09cd4bf7e 100644
> > --- a/nfs4.1/server41tests/st_dir_deleg.py
> > +++ b/nfs4.1/server41tests/st_dir_deleg.py
> > @@ -175,3 +175,36 @@ def testDirDelegRemoveRecall(t, env):
> > ops = [ op.putfh(fh), op.delegreturn(deleg) ]
> > res = sess1.compound(ops)
> > check(res)
> > +
> > +def testDirDelegRenameRecall(t, env):
> > + """Verify rename triggers dir delegation recall
> > +
> > + FLAGS: dirdeleg all
> > + CODE: DIRDELEG4
> > + """
> > + c = env.c1
> > + recall = threading.Event()
> > + sess1, fh, deleg = _getDirDeleg(t, env, [], recall)
> > +
> > + # Create a file from sess1
> > + claim = open_claim4(CLAIM_NULL, env.testname(t))
> > + owner = open_owner4(0, b"owner")
> > + how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
> > + open_op = [ op.putfh(fh), op.open(0,
> > + OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
> > + OPEN4_SHARE_DENY_NONE, owner, how, claim) ]
> > + res = sess1.compound(open_op)
> > + check(res)
>
> You probably want to close this file at the end of the test so you're
> not leaving state behind on the server.
>
Definitely. I'll make a pass through all of the tests and make sure
that there is no leftover state. I'll plan to send a v3.
Thanks for the review!
>
--
Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH pynfs v2 08/25] server41tests: test mkdir triggers dir delegation recall
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
` (6 preceding siblings ...)
2026-04-16 18:14 ` [PATCH pynfs v2 07/25] server41tests: test rename " Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 09/25] server41tests: test link " Jeff Layton
` (16 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
Get a dir delegation with no notification mask, then create a
subdirectory from a second client. Verify that the server issues
a CB_RECALL.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index d8d09cd4bf7e..4c483950fd4a 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -208,3 +208,26 @@ def testDirDelegRenameRecall(t, env):
ops = [ op.putfh(fh), op.delegreturn(deleg) ]
res = sess1.compound(ops)
check(res)
+
+def testDirDelegMkdirRecall(t, env):
+ """Verify mkdir triggers dir delegation recall
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG5
+ """
+ c = env.c1
+ recall = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env, [], recall)
+
+ # Create a subdirectory from sess2 -- should trigger recall
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ create_op = [ op.putfh(fh),
+ op.create(createtype4(NF4DIR), env.testname(t),
+ {FATTR4_MODE: 0o755}) ]
+ slot = sess2.compound_async(create_op)
+ completed = recall.wait(2)
+ env.sleep(.1)
+
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+ check(res)
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v2 09/25] server41tests: test link triggers dir delegation recall
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
` (7 preceding siblings ...)
2026-04-16 18:14 ` [PATCH pynfs v2 08/25] server41tests: test mkdir " Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 10/25] server41tests: test no notifications without GFLAG_EXTEND Jeff Layton
` (15 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
Get a dir delegation with no notification mask, create a file, then
hard-link it to a new name from a second client. Verify that the
server issues a CB_RECALL.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 4c483950fd4a..808323b89ffa 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -231,3 +231,39 @@ def testDirDelegMkdirRecall(t, env):
ops = [ op.putfh(fh), op.delegreturn(deleg) ]
res = sess1.compound(ops)
check(res)
+
+def testDirDelegLinkRecall(t, env):
+ """Verify link triggers dir delegation recall
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG6
+ """
+ c = env.c1
+ recall = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env, [], recall)
+
+ # Create a file from sess1
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess1.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+ close_file(sess1, file_fh, stateid=open_stateid)
+
+ # Link the file to a new name from sess2 -- should trigger recall
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ link_op = [ op.putfh(file_fh), op.savefh(),
+ op.putfh(fh),
+ op.link(b"%s_link" % env.testname(t)) ]
+ slot = sess2.compound_async(link_op)
+ completed = recall.wait(2)
+ env.sleep(.1)
+
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+ check(res)
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v2 10/25] server41tests: test no notifications without GFLAG_EXTEND
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
` (8 preceding siblings ...)
2026-04-16 18:14 ` [PATCH pynfs v2 09/25] server41tests: test link " Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 11/25] server41tests: test unrequested notification type triggers recall Jeff Layton
` (14 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
Request a dir delegation with NOTIFY4_ADD_ENTRY but without
NOTIFY4_GFLAG_EXTEND. Verify the server issues a CB_RECALL
instead of CB_NOTIFY when a file is created.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 808323b89ffa..7d1e664a7923 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -267,3 +267,33 @@ def testDirDelegLinkRecall(t, env):
ops = [ op.putfh(fh), op.delegreturn(deleg) ]
res = sess1.compound(ops)
check(res)
+
+def testDirDelegNoGflag(t, env):
+ """Verify recall instead of notification without NOTIFY4_GFLAG_EXTEND
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG7
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env, [NOTIFY4_ADD_ENTRY], cb)
+
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim) ]
+ slot = sess2.compound_async(open_op)
+ completed = cb.wait(2)
+ env.sleep(.1)
+
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+ check(res)
+
+ if cb.got_notify:
+ fail("Got CB_NOTIFY without GFLAG_EXTEND")
+ if not cb.got_recall:
+ fail("Expected CB_RECALL without GFLAG_EXTEND, but didn't get one")
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v2 11/25] server41tests: test unrequested notification type triggers recall
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
` (9 preceding siblings ...)
2026-04-16 18:14 ` [PATCH pynfs v2 10/25] server41tests: test no notifications without GFLAG_EXTEND Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 12/25] server41tests: add a test for removal from dir with dir delegation Jeff Layton
` (13 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
Request a dir delegation with only REMOVE notifications. Trigger
an ADD event from a second client. Verify the server issues a
CB_RECALL instead of CB_NOTIFY since ADD was not requested.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 7d1e664a7923..d62ccb634056 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -297,3 +297,35 @@ def testDirDelegNoGflag(t, env):
fail("Got CB_NOTIFY without GFLAG_EXTEND")
if not cb.got_recall:
fail("Expected CB_RECALL without GFLAG_EXTEND, but didn't get one")
+
+def testDirDelegFiltering(t, env):
+ """Verify unrequested notification type triggers recall
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG8
+ """
+ c = env.c1
+ cb = threading.Event()
+ # Only request REMOVE notifications
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_REMOVE_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ # Trigger an ADD event (not requested) from a second client
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim) ]
+ slot = sess2.compound_async(open_op)
+ completed = cb.wait(2)
+ env.sleep(.1)
+
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+ check(res)
+
+ if not cb.got_recall:
+ fail("Expected CB_RECALL for unrequested notification type")
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v2 12/25] server41tests: add a test for removal from dir with dir delegation
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
` (10 preceding siblings ...)
2026-04-16 18:14 ` [PATCH pynfs v2 11/25] server41tests: test unrequested notification type triggers recall Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 13/25] server41tests: add a test for directory add notifications Jeff Layton
` (12 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
Request a dir delegation with REMOVE_ENTRY notifications. Create a
file, then remove it from a second client. Verify the server sends
a CB_NOTIFY with the correct REMOVE event.
Also add full child and directory attribute bitmaps to the
GET_DIR_DELEGATION request for all CB_NOTIFY tests.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 65 ++++++++++++++++++++++++++++++++++--
1 file changed, 63 insertions(+), 2 deletions(-)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index d62ccb634056..9c348e9e80f6 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -68,8 +68,26 @@ def _getDirDeleg(t, env, notify_mask, cb):
mask_bm = nfs4lib.list2bitmap(notify_mask)
ops = [ op.putfh(fh), op.get_dir_delegation(False, nfs4lib.list2bitmap(notify_mask),
zerotime, zerotime,
- nfs4lib.list2bitmap([]),
- nfs4lib.list2bitmap([]))]
+ nfs4lib.list2bitmap([FATTR4_TYPE,
+ FATTR4_CHANGE,
+ FATTR4_SIZE,
+ FATTR4_FILEID,
+ FATTR4_FILEHANDLE,
+ FATTR4_MODE,
+ FATTR4_NUMLINKS,
+ FATTR4_RAWDEV,
+ FATTR4_SPACE_USED,
+ FATTR4_TIME_ACCESS,
+ FATTR4_TIME_METADATA,
+ FATTR4_TIME_MODIFY,
+ FATTR4_TIME_CREATE]),
+ nfs4lib.list2bitmap([FATTR4_CHANGE,
+ FATTR4_SIZE,
+ FATTR4_NUMLINKS,
+ FATTR4_SPACE_USED,
+ FATTR4_TIME_ACCESS,
+ FATTR4_TIME_METADATA,
+ FATTR4_TIME_MODIFY]))]
res = sess1.compound(ops)
check(res, [NFS4_OK, NFS4ERR_NOTSUPP])
if (res.status == NFS4ERR_NOTSUPP):
@@ -329,3 +347,46 @@ def testDirDelegFiltering(t, env):
if not cb.got_recall:
fail("Expected CB_RECALL for unrequested notification type")
+
+def testDirDelegRemove(t, env):
+ """Create a dir_deleg that accepts notification of REMOVE events
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG9
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_CHANGE_DIR_ATTRS,
+ NOTIFY4_REMOVE_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess1.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+ close_file(sess1, file_fh, stateid=open_stateid)
+
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ remove_op = [ op.putfh(fh), op.remove(env.testname(t)) ]
+ res = sess2.compound(remove_op)
+ check(res)
+
+ completed = cb.wait(5)
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ evt_type, evt = decode_notify_event(cb.changes[0])
+ if evt_type != NOTIFY4_REMOVE_ENTRY:
+ fail("Expected REMOVE notification, got %d" % evt_type)
+ if evt.nrm_old_entry.ne_file != env.testname(t):
+ fail("Wrong entry name in REMOVE notification")
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v2 13/25] server41tests: add a test for directory add notifications
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
` (11 preceding siblings ...)
2026-04-16 18:14 ` [PATCH pynfs v2 12/25] server41tests: add a test for removal from dir with dir delegation Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 14/25] server41tests: add test for RENAME event notifications Jeff Layton
` (11 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
Request a dir delegation with ADD_ENTRY notifications. Create a file
from a second client. Verify the server sends a CB_NOTIFY with the
correct ADD event.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 41 ++++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 9c348e9e80f6..b26ed1f6e333 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -390,3 +390,44 @@ def testDirDelegRemove(t, env):
fail("Expected REMOVE notification, got %d" % evt_type)
if evt.nrm_old_entry.ne_file != env.testname(t):
fail("Wrong entry name in REMOVE notification")
+
+def testDirDelegAdd(t, env):
+ """Create a dir_deleg that accepts notification of ADD events
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG10
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_ADD_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess2.compound(open_op)
+ check(res)
+
+ completed = cb.wait(2)
+
+ delegreturn_op = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(delegreturn_op)
+ check(res)
+
+ remove_op = [ op.putfh(fh), op.remove(env.testname(t)) ]
+ res = sess2.compound(remove_op)
+ check(res)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ evt_type, evt = decode_notify_event(cb.changes[0])
+ if evt_type != NOTIFY4_ADD_ENTRY:
+ fail("Expected ADD notification, got %d" % evt_type)
+ if evt.nad_new_entry.ne_file != env.testname(t):
+ fail("Wrong entry name in ADD notification")
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v2 14/25] server41tests: add test for RENAME event notifications
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
` (12 preceding siblings ...)
2026-04-16 18:14 ` [PATCH pynfs v2 13/25] server41tests: add a test for directory add notifications Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 15/25] server41tests: verify child attributes in ADD notification Jeff Layton
` (10 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
Request a dir delegation with RENAME_ENTRY notifications. Create a
file, then rename it from a second client. Verify the server sends
a CB_NOTIFY with the correct RENAME event.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 44 ++++++++++++++++++++++++++++++++++++
1 file changed, 44 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index b26ed1f6e333..2c1cb846cb4b 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -431,3 +431,47 @@ def testDirDelegAdd(t, env):
fail("Expected ADD notification, got %d" % evt_type)
if evt.nad_new_entry.ne_file != env.testname(t):
fail("Wrong entry name in ADD notification")
+
+def testDirDelegRename(t, env):
+ """Create a dir_deleg that accepts notification of RENAME events
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG11
+ """
+ c = env.c1
+ cb = threading.Event()
+
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_RENAME_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim) ]
+ res = sess1.compound(open_op)
+ check(res)
+
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ topdir = c.homedir + [t.code.encode('utf8')]
+ oldpath = topdir + [env.testname(t)]
+ newpath = topdir + [b"%s_2" % env.testname(t)]
+ res = rename_obj(sess2, oldpath, newpath)
+ check(res)
+
+ completed = cb.wait(2)
+
+ delegreturn_op = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(delegreturn_op)
+ check(res)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ evt_type, evt = decode_notify_event(cb.changes[0])
+ if evt_type != NOTIFY4_RENAME_ENTRY:
+ fail("Expected RENAME notification, got %d" % evt_type)
+ if evt.nrn_old_entry.nrm_old_entry.ne_file != env.testname(t):
+ fail("Wrong old entry name in RENAME notification")
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v2 15/25] server41tests: verify child attributes in ADD notification
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
` (13 preceding siblings ...)
2026-04-16 18:14 ` [PATCH pynfs v2 14/25] server41tests: add test for RENAME event notifications Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 16/25] server41tests: test CHANGE_DIR_ATTRS notification Jeff Layton
` (9 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
Request a dir delegation with ADD_ENTRY notifications. Create a file
from a second client. Verify the ADD notification includes child
attributes with a correct size.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 47 ++++++++++++++++++++++++++++++++++++
1 file changed, 47 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 2c1cb846cb4b..b5b81b3e509e 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -475,3 +475,50 @@ def testDirDelegRename(t, env):
fail("Expected RENAME notification, got %d" % evt_type)
if evt.nrn_old_entry.nrm_old_entry.ne_file != env.testname(t):
fail("Wrong old entry name in RENAME notification")
+
+def testDirDelegChildAttrs(t, env):
+ """Verify child attributes are present in ADD notification
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG12
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_ADD_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess2.compound(open_op)
+ check(res)
+
+ completed = cb.wait(2)
+
+ delegreturn_op = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(delegreturn_op)
+ check(res)
+
+ remove_op = [ op.putfh(fh), op.remove(env.testname(t)) ]
+ res = sess2.compound(remove_op)
+ check(res)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ evt_type, evt = decode_notify_event(cb.changes[0])
+ if evt_type != NOTIFY4_ADD_ENTRY:
+ fail("Expected ADD notification, got %d" % evt_type)
+
+ attrs = evt.nad_new_entry.ne_attrs
+ if not any(attrs.attrmask):
+ fail("No child attributes in ADD notification")
+ attrs.attrmask = bitmap4_to_int(attrs.attrmask)
+ attr_dict = nfs4lib.fattr2dict(attrs)
+ if FATTR4_SIZE in attr_dict and attr_dict[FATTR4_SIZE] != 0:
+ fail("Expected size 0 for new file, got %d" % attr_dict[FATTR4_SIZE])
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v2 16/25] server41tests: test CHANGE_DIR_ATTRS notification
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
` (14 preceding siblings ...)
2026-04-16 18:14 ` [PATCH pynfs v2 15/25] server41tests: verify child attributes in ADD notification Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 17/25] server41tests: test mkdir triggers ADD notification Jeff Layton
` (8 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
Request a dir delegation with CHANGE_DIR_ATTRS and ADD_ENTRY
notifications. Create a file from a second client. Verify the
server sends a CHANGE_DIR_ATTRS notification with directory
attributes.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 50 ++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index b5b81b3e509e..e4d922be9d2c 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -522,3 +522,53 @@ def testDirDelegChildAttrs(t, env):
attr_dict = nfs4lib.fattr2dict(attrs)
if FATTR4_SIZE in attr_dict and attr_dict[FATTR4_SIZE] != 0:
fail("Expected size 0 for new file, got %d" % attr_dict[FATTR4_SIZE])
+
+def testDirDelegDirAttrs(t, env):
+ """Verify CHANGE_DIR_ATTRS notification on directory change
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG13
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_CHANGE_DIR_ATTRS,
+ NOTIFY4_ADD_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess2.compound(open_op)
+ check(res)
+
+ completed = cb.wait(2)
+
+ delegreturn_op = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(delegreturn_op)
+ check(res)
+
+ remove_op = [ op.putfh(fh), op.remove(env.testname(t)) ]
+ res = sess2.compound(remove_op)
+ check(res)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ # Look for a CHANGE_DIR_ATTRS event among the changes
+ found_dir_attrs = False
+ for change in cb.changes:
+ evt_type, evt = decode_notify_event(change)
+ if evt_type == NOTIFY4_CHANGE_DIR_ATTRS:
+ found_dir_attrs = True
+ attrs = evt.na_changed_entry.ne_attrs
+ if not any(attrs.attrmask):
+ fail("No directory attributes in CHANGE_DIR_ATTRS notification")
+ break
+
+ if not found_dir_attrs:
+ fail("No CHANGE_DIR_ATTRS notification found")
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v2 17/25] server41tests: test mkdir triggers ADD notification
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
` (15 preceding siblings ...)
2026-04-16 18:14 ` [PATCH pynfs v2 16/25] server41tests: test CHANGE_DIR_ATTRS notification Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 18/25] server41tests: test DELEGRETURN stops notifications Jeff Layton
` (7 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
Request a dir delegation with ADD_ENTRY notifications. Create a
subdirectory from a second client. Verify the server sends a
CB_NOTIFY with the correct ADD event.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 33 +++++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index e4d922be9d2c..282fbbb9e09c 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -572,3 +572,36 @@ def testDirDelegDirAttrs(t, env):
if not found_dir_attrs:
fail("No CHANGE_DIR_ATTRS notification found")
+
+def testDirDelegMkdir(t, env):
+ """Verify mkdir triggers ADD notification
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG14
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_ADD_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ topdir = c.homedir + [t.code.encode('utf8')]
+ subdir = topdir + [env.testname(t)]
+ res = create_obj(sess2, subdir)
+ check(res)
+
+ completed = cb.wait(2)
+
+ delegreturn_op = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(delegreturn_op)
+ check(res)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ evt_type, evt = decode_notify_event(cb.changes[0])
+ if evt_type != NOTIFY4_ADD_ENTRY:
+ fail("Expected ADD notification, got %d" % evt_type)
+ if evt.nad_new_entry.ne_file != env.testname(t):
+ fail("Wrong directory name in ADD notification")
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v2 18/25] server41tests: test DELEGRETURN stops notifications
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
` (16 preceding siblings ...)
2026-04-16 18:14 ` [PATCH pynfs v2 17/25] server41tests: test mkdir triggers ADD notification Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 19/25] server41tests: verify filehandle in ADD notification Jeff Layton
` (6 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
Get a dir delegation with ADD_ENTRY notifications, immediately
return it with DELEGRETURN, then create a file from a second
client. Verify no callbacks are received after the delegation
was returned.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 38 ++++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 282fbbb9e09c..376c49c71d7e 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -605,3 +605,41 @@ def testDirDelegMkdir(t, env):
fail("Expected ADD notification, got %d" % evt_type)
if evt.nad_new_entry.ne_file != env.testname(t):
fail("Wrong directory name in ADD notification")
+
+def testDirDelegReturnStopsNotify(t, env):
+ """Verify DELEGRETURN stops notifications
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG15
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_ADD_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ # Immediately return the delegation
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+ check(res)
+
+ # Now create a file from a second client
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim) ]
+ res = sess2.compound(open_op)
+ check(res)
+
+ # Wait briefly -- should NOT get any callback
+ completed = cb.wait(2)
+
+ remove_op = [ op.putfh(fh), op.remove(env.testname(t)) ]
+ res = sess2.compound(remove_op)
+ check(res)
+
+ if cb.got_notify or cb.got_recall:
+ fail("Received callback after DELEGRETURN")
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v2 19/25] server41tests: verify filehandle in ADD notification
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
` (17 preceding siblings ...)
2026-04-16 18:14 ` [PATCH pynfs v2 18/25] server41tests: test DELEGRETURN stops notifications Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 20/25] server41tests: test cross-directory rename REMOVE notification Jeff Layton
` (5 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
Request a dir delegation with ADD_ENTRY notifications. Create a file
from a second client, recording its filehandle via GETFH. Verify the
ADD notification includes a filehandle attribute that matches.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 48 ++++++++++++++++++++++++++++++++++++
1 file changed, 48 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 376c49c71d7e..8ad34881e694 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -643,3 +643,51 @@ def testDirDelegReturnStopsNotify(t, env):
if cb.got_notify or cb.got_recall:
fail("Received callback after DELEGRETURN")
+
+def testDirDelegFilehandle(t, env):
+ """Verify filehandle in ADD notification matches GETFH result
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG16
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_ADD_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess2.compound(open_op)
+ check(res)
+ file_fh = res.resarray[-1].object
+
+ completed = cb.wait(2)
+
+ delegreturn_op = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(delegreturn_op)
+ check(res)
+
+ remove_op = [ op.putfh(fh), op.remove(env.testname(t)) ]
+ res = sess2.compound(remove_op)
+ check(res)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ evt_type, evt = decode_notify_event(cb.changes[0])
+ if evt_type != NOTIFY4_ADD_ENTRY:
+ fail("Expected ADD notification, got %d" % evt_type)
+
+ attrs = evt.nad_new_entry.ne_attrs
+ attrs.attrmask = bitmap4_to_int(attrs.attrmask)
+ attr_dict = nfs4lib.fattr2dict(attrs)
+ if FATTR4_FILEHANDLE not in attr_dict:
+ fail("No filehandle in ADD notification attributes")
+ if attr_dict[FATTR4_FILEHANDLE] != file_fh:
+ fail("Filehandle in notification doesn't match GETFH result")
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v2 20/25] server41tests: test cross-directory rename REMOVE notification
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
` (18 preceding siblings ...)
2026-04-16 18:14 ` [PATCH pynfs v2 19/25] server41tests: verify filehandle in ADD notification Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 21/25] server41tests: test cross-directory rename ADD notification on target Jeff Layton
` (4 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
Verify that a cross-directory rename generates a NOTIFY4_REMOVE_ENTRY
notification on the source directory's delegation, rather than a
NOTIFY4_RENAME_ENTRY (which is only for within-directory renames).
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 57 ++++++++++++++++++++++++++++++++++++
1 file changed, 57 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 8ad34881e694..35b6fea6d904 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -691,3 +691,60 @@ def testDirDelegFilehandle(t, env):
fail("No filehandle in ADD notification attributes")
if attr_dict[FATTR4_FILEHANDLE] != file_fh:
fail("Filehandle in notification doesn't match GETFH result")
+
+def testDirDelegCrossRename(t, env):
+ """Verify cross-directory rename generates REMOVE notification on source
+
+ Per RFC 8881bis Section 27.4.6, a rename across directories sends
+ a REMOVE notification to the source directory and an ADD notification
+ to the target directory, rather than a RENAME notification.
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG17
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_REMOVE_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ # Create a file in the delegated directory
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess1.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+ close_file(sess1, file_fh, stateid=open_stateid)
+
+ # Create a target directory (sibling of the delegated directory)
+ topdir = c.homedir + [t.code.encode('utf8')]
+ targetdir = c.homedir + [b"%s_target" % t.code.encode('utf8')]
+ res = create_obj(sess1, targetdir)
+ check(res)
+
+ # Rename the file from the delegated dir to the target dir from sess2
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ oldpath = topdir + [env.testname(t)]
+ newpath = targetdir + [env.testname(t)]
+ res = rename_obj(sess2, oldpath, newpath)
+ check(res)
+
+ completed = cb.wait(2)
+
+ delegreturn_op = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(delegreturn_op)
+ check(res)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ evt_type, evt = decode_notify_event(cb.changes[0])
+ if evt_type != NOTIFY4_REMOVE_ENTRY:
+ fail("Expected REMOVE notification for cross-dir rename, got %d" % evt_type)
+ if evt.nrm_old_entry.ne_file != env.testname(t):
+ fail("Wrong entry name in REMOVE notification")
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v2 21/25] server41tests: test cross-directory rename ADD notification on target
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
` (19 preceding siblings ...)
2026-04-16 18:14 ` [PATCH pynfs v2 20/25] server41tests: test cross-directory rename REMOVE notification Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 22/25] server41tests: test link triggers ADD notification Jeff Layton
` (3 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
Verify that a cross-directory rename generates a NOTIFY4_ADD_ENTRY
notification on the target directory's delegation.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 60 ++++++++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 35b6fea6d904..fab4d64eed42 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -748,3 +748,63 @@ def testDirDelegCrossRename(t, env):
fail("Expected REMOVE notification for cross-dir rename, got %d" % evt_type)
if evt.nrm_old_entry.ne_file != env.testname(t):
fail("Wrong entry name in REMOVE notification")
+
+def testDirDelegCrossRenameTarget(t, env):
+ """Verify cross-directory rename generates ADD notification on target
+
+ Per RFC 8881bis Section 27.4.6, a rename across directories sends
+ a REMOVE notification to the source directory and an ADD notification
+ to the target directory.
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG18
+ """
+ c = env.c1
+ cb = threading.Event()
+
+ # Create a source directory (not delegated) and a file in it
+ srcdir = c.homedir + [b"%s_src" % t.code.encode('utf8')]
+ sess1 = c.new_client_session(b"%s_1" % env.testname(t))
+ res = create_obj(sess1, srcdir)
+ check(res)
+
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ src_fh = res.resarray[-1].object
+ open_op = [ op.putfh(src_fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess1.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+ close_file(sess1, file_fh, stateid=open_stateid)
+
+ # Get a dir delegation on the target directory
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_ADD_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ # Rename the file into the delegated target directory from sess2
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ topdir = c.homedir + [t.code.encode('utf8')]
+ oldpath = srcdir + [env.testname(t)]
+ newpath = topdir + [env.testname(t)]
+ res = rename_obj(sess2, oldpath, newpath)
+ check(res)
+
+ completed = cb.wait(2)
+
+ delegreturn_op = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(delegreturn_op)
+ check(res)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ evt_type, evt = decode_notify_event(cb.changes[0])
+ if evt_type != NOTIFY4_ADD_ENTRY:
+ fail("Expected ADD notification for cross-dir rename target, got %d" % evt_type)
+ if evt.nad_new_entry.ne_file != env.testname(t):
+ fail("Wrong entry name in ADD notification")
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v2 22/25] server41tests: test link triggers ADD notification
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
` (20 preceding siblings ...)
2026-04-16 18:14 ` [PATCH pynfs v2 21/25] server41tests: test cross-directory rename ADD notification on target Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 23/25] server41tests: test same-client changes don't trigger notifications Jeff Layton
` (2 subsequent siblings)
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
Verify that creating a hard link triggers a NOTIFY4_ADD_ENTRY
notification on the directory's delegation.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 56 ++++++++++++++++++++++++++++++++++++
1 file changed, 56 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index fab4d64eed42..7c230d40e808 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -808,3 +808,59 @@ def testDirDelegCrossRenameTarget(t, env):
fail("Expected ADD notification for cross-dir rename target, got %d" % evt_type)
if evt.nad_new_entry.ne_file != env.testname(t):
fail("Wrong entry name in ADD notification")
+
+def testDirDelegLinkNotify(t, env):
+ """Verify hard link triggers ADD notification
+
+ Per RFC 8881bis Section 27.4.4, the server sends an ADD notification
+ when a hard link is being created to an existing file.
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG19
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_ADD_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ # Create a file in the delegated directory from sess1
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess1.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+ close_file(sess1, file_fh, stateid=open_stateid)
+
+ # Clear the notification state from the create
+ cb.clear()
+ cb.got_notify = False
+
+ # Link the file to a new name from sess2
+ link_name = b"%s_link" % env.testname(t)
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ link_op = [ op.putfh(file_fh), op.savefh(),
+ op.putfh(fh),
+ op.link(link_name) ]
+ res = sess2.compound(link_op)
+ check(res)
+
+ completed = cb.wait(2)
+
+ delegreturn_op = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(delegreturn_op)
+ check(res)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ evt_type, evt = decode_notify_event(cb.changes[0])
+ if evt_type != NOTIFY4_ADD_ENTRY:
+ fail("Expected ADD notification for link, got %d" % evt_type)
+ if evt.nad_new_entry.ne_file != link_name:
+ fail("Wrong entry name in ADD notification")
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v2 23/25] server41tests: test same-client changes don't trigger notifications
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
` (21 preceding siblings ...)
2026-04-16 18:14 ` [PATCH pynfs v2 22/25] server41tests: test link triggers ADD notification Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 24/25] server41tests: test cross-directory rename-over nad_old_entry Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 25/25] server41tests: test within-directory " Jeff Layton
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
Verify that changes made by the delegation-holding session itself
do not trigger CB_NOTIFY or CB_RECALL callbacks.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
nfs4.1/server41tests/st_dir_deleg.py | 37 ++++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 7c230d40e808..63072bd8b81a 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -864,3 +864,40 @@ def testDirDelegLinkNotify(t, env):
fail("Expected ADD notification for link, got %d" % evt_type)
if evt.nad_new_entry.ne_file != link_name:
fail("Wrong entry name in ADD notification")
+
+def testDirDelegSameClientNoNotify(t, env):
+ """Verify delegation holder's own changes don't trigger notifications
+
+ Per RFC 8881bis Section 16.2.11.2, order-unaware clients should not
+ receive notifications for changes they made themselves.
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG20
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_ADD_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ # Create a file from the delegation-holding session itself
+ claim = open_claim4(CLAIM_NULL, env.testname(t))
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim) ]
+ res = sess1.compound(open_op)
+ check(res)
+
+ # Wait briefly -- should NOT get any callback
+ completed = cb.wait(2)
+
+ ops = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(ops)
+ check(res)
+
+ if cb.got_notify:
+ fail("Got CB_NOTIFY for delegation holder's own change")
+ if cb.got_recall:
+ fail("Got CB_RECALL for delegation holder's own change")
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v2 24/25] server41tests: test cross-directory rename-over nad_old_entry
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
` (22 preceding siblings ...)
2026-04-16 18:14 ` [PATCH pynfs v2 23/25] server41tests: test same-client changes don't trigger notifications Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
2026-04-16 18:14 ` [PATCH pynfs v2 25/25] server41tests: test within-directory " Jeff Layton
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
Test that when a cross-directory rename overwrites an existing file in
a delegated directory, the server reports the overwritten entry via
nad_old_entry in the NOTIFY4_ADD_ENTRY notification rather than
generating a separate NOTIFY4_REMOVE_ENTRY notification.
Per RFC 8881 Section 18.26.4, when the removal is done atomically with
the rename, a separate NOTIFY4_REMOVE_ENTRY notification will not be
generated. Instead, the deletion of the file will be reported as part
of the NOTIFY4_ADD_ENTRY notification via nad_old_entry.
Also fix _getDirDeleg to initialize cb.changes as a list and use
extend() to accumulate notifications, so tests that receive multiple
CB_NOTIFY callbacks can see all notifications.
---
nfs4.1/server41tests/st_dir_deleg.py | 100 ++++++++++++++++++++++++++++++++++-
1 file changed, 99 insertions(+), 1 deletion(-)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index 63072bd8b81a..a2465efbcdbe 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -44,7 +44,7 @@ def _getDirDeleg(t, env, notify_mask, cb):
def notify_pre_hook(arg, env):
cb.stateid = arg.cna_stateid
cb.fh = arg.cna_fh
- cb.changes = arg.cna_changes
+ cb.changes.extend(arg.cna_changes)
cb.got_notify = True
env.notify = cb.set # This is called after compound sent to queue
def notify_post_hook(arg, env, res):
@@ -52,6 +52,7 @@ def _getDirDeleg(t, env, notify_mask, cb):
cb.got_recall = False
cb.got_notify = False
+ cb.changes = []
c = env.c1
sess1 = c.new_client_session(b"%s_1" % env.testname(t))
@@ -901,3 +902,100 @@ def testDirDelegSameClientNoNotify(t, env):
fail("Got CB_NOTIFY for delegation holder's own change")
if cb.got_recall:
fail("Got CB_RECALL for delegation holder's own change")
+
+def testDirDelegCrossRenameOver(t, env):
+ """Verify cross-directory rename-over reports overwritten entry in nad_old_entry
+
+ Per RFC 8881 Section 18.26.4, when a cross-directory rename
+ overwrites an existing file in the target directory, a
+ NOTIFY4_ADD_ENTRY is generated. When the removal is done
+ atomically with the rename, a separate NOTIFY4_REMOVE_ENTRY
+ notification will not be generated. Instead, the deletion of the
+ file will be reported as part of the NOTIFY4_ADD_ENTRY notification
+ via nad_old_entry.
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG21
+ """
+ c = env.c1
+ cb = threading.Event()
+
+ # Get a dir delegation on the target directory
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_ADD_ENTRY,
+ NOTIFY4_REMOVE_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ topdir = c.homedir + [t.code.encode('utf8')]
+
+ # Create a file in the delegated directory (will be renamed over)
+ victim_name = b"%s_victim" % env.testname(t)
+ claim = open_claim4(CLAIM_NULL, victim_name)
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess1.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+ close_file(sess1, file_fh, stateid=open_stateid)
+
+ # Create a source directory and a file in it
+ srcdir = c.homedir + [b"%s_src" % t.code.encode('utf8')]
+ res = create_obj(sess1, srcdir)
+ check(res)
+ src_fh = res.resarray[-1].object
+
+ src_name = env.testname(t)
+ claim2 = open_claim4(CLAIM_NULL, src_name)
+ owner2 = open_owner4(0, b"owner2")
+ how2 = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+ open_op = [ op.putfh(src_fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner2, how2, claim2), op.getfh() ]
+ res = sess1.compound(open_op)
+ check(res)
+ open_stateid2 = res.resarray[-2].stateid
+ file_fh2 = res.resarray[-1].object
+ close_file(sess1, file_fh2, stateid=open_stateid2)
+
+ # Clear notification state from creates above
+ cb.clear()
+ cb.got_notify = False
+
+ # Rename the source file over the victim in the delegated dir from sess2
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ oldpath = srcdir + [src_name]
+ newpath = topdir + [victim_name]
+ res = rename_obj(sess2, oldpath, newpath)
+ check(res)
+
+ completed = cb.wait(2)
+ if completed:
+ cb.clear()
+ cb.wait(1)
+
+ delegreturn_op = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(delegreturn_op)
+ check(res)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ # Look for ADD notification with nad_old_entry for the overwritten file
+ got_add = False
+ for change in cb.changes:
+ evt_type, evt = decode_notify_event(change)
+ if evt_type == NOTIFY4_ADD_ENTRY:
+ got_add = True
+ if evt.nad_new_entry.ne_file != victim_name:
+ fail("Wrong entry name in ADD notification")
+ if len(evt.nad_old_entry) != 1:
+ fail("Expected nad_old_entry to contain the overwritten entry")
+ if evt.nad_old_entry[0].nrm_old_entry.ne_file != victim_name:
+ fail("Wrong overwritten entry name in nad_old_entry")
+
+ if not got_add:
+ fail("Missing ADD notification for cross-dir rename-over")
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH pynfs v2 25/25] server41tests: test within-directory rename-over nad_old_entry
2026-04-16 18:14 [PATCH pynfs v2 00/25] nfs4.1: add some directory delegation testcases Jeff Layton
` (23 preceding siblings ...)
2026-04-16 18:14 ` [PATCH pynfs v2 24/25] server41tests: test cross-directory rename-over nad_old_entry Jeff Layton
@ 2026-04-16 18:14 ` Jeff Layton
24 siblings, 0 replies; 28+ messages in thread
From: Jeff Layton @ 2026-04-16 18:14 UTC (permalink / raw)
To: Calum Mackay
Cc: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, linux-nfs, Jeff Layton
Test that when a within-directory rename overwrites an existing entry,
the server populates nrn_new_entry.nad_old_entry in the
NOTIFY4_RENAME_ENTRY notification with the overwritten entry's info.
---
nfs4.1/server41tests/st_dir_deleg.py | 65 ++++++++++++++++++++++++++++++++++++
1 file changed, 65 insertions(+)
diff --git a/nfs4.1/server41tests/st_dir_deleg.py b/nfs4.1/server41tests/st_dir_deleg.py
index a2465efbcdbe..3a1d7ff2b659 100644
--- a/nfs4.1/server41tests/st_dir_deleg.py
+++ b/nfs4.1/server41tests/st_dir_deleg.py
@@ -999,3 +999,68 @@ def testDirDelegCrossRenameOver(t, env):
if not got_add:
fail("Missing ADD notification for cross-dir rename-over")
+
+def testDirDelegRenameOver(t, env):
+ """Verify within-directory rename-over populates nad_old_entry
+
+ Per RFC 8881bis Section 27.4.6, when a within-directory rename
+ overwrites an existing entry, the overwritten entry's info is
+ reported in nrn_new_entry.nad_old_entry.
+
+ FLAGS: dirdeleg all
+ CODE: DIRDELEG22
+ """
+ c = env.c1
+ cb = threading.Event()
+ sess1, fh, deleg = _getDirDeleg(t, env,
+ [NOTIFY4_RENAME_ENTRY,
+ NOTIFY4_GFLAG_EXTEND], cb)
+
+ # Create two files in the delegated directory from sess1
+ src_name = env.testname(t)
+ victim_name = b"%s_victim" % env.testname(t)
+ owner = open_owner4(0, b"owner")
+ how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
+
+ for name in [src_name, victim_name]:
+ claim = open_claim4(CLAIM_NULL, name)
+ open_op = [ op.putfh(fh), op.open(0,
+ OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+ OPEN4_SHARE_DENY_NONE, owner, how, claim), op.getfh() ]
+ res = sess1.compound(open_op)
+ check(res)
+ open_stateid = res.resarray[-2].stateid
+ file_fh = res.resarray[-1].object
+ close_file(sess1, file_fh, stateid=open_stateid)
+
+ # Rename src over victim from sess2
+ sess2 = c.new_client_session(b"%s_2" % env.testname(t))
+ rename_op = [ op.putfh(fh), op.savefh(),
+ op.putfh(fh),
+ op.rename(src_name, victim_name) ]
+ res = sess2.compound(rename_op)
+ check(res)
+
+ completed = cb.wait(2)
+ if completed:
+ cb.clear()
+ cb.wait(1)
+
+ delegreturn_op = [ op.putfh(fh), op.delegreturn(deleg) ]
+ res = sess1.compound(delegreturn_op)
+ check(res)
+
+ if (not completed or not cb.got_notify):
+ fail("Didn't receive a CB_NOTIFY from the server!")
+
+ evt_type, evt = decode_notify_event(cb.changes[0])
+ if evt_type != NOTIFY4_RENAME_ENTRY:
+ fail("Expected RENAME notification, got %d" % evt_type)
+ if evt.nrn_old_entry.nrm_old_entry.ne_file != src_name:
+ fail("Wrong old entry name in RENAME notification")
+ if evt.nrn_new_entry.nad_new_entry.ne_file != victim_name:
+ fail("Wrong new entry name in RENAME notification")
+ if len(evt.nrn_new_entry.nad_old_entry) != 1:
+ fail("Expected nad_old_entry to contain the overwritten entry")
+ if evt.nrn_new_entry.nad_old_entry[0].nrm_old_entry.ne_file != victim_name:
+ fail("Wrong overwritten entry name in nad_old_entry")
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread