* [PATCH v4 0/9] nfs: prepare for locking changes.
@ 2026-10-28 4:06 NeilBrown
2026-10-28 4:06 ` [PATCH v4 1/9] nfs: fix open-blocking with d_fsdata NeilBrown
` (9 more replies)
0 siblings, 10 replies; 11+ messages in thread
From: NeilBrown @ 2026-10-28 4:06 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Alexander Viro,
Christian Brauner, Jonathan Flynn
Cc: Jeff Layton, Jan Kara, linux-fsdevel, linux-nfs, linux-kernel
Now that the requirement for this are upstream this could go via the NFS
tree or the VFS tree.
Thanks,
NeilBrown
This v4 fixes the problems with patch 1 which Jonathan Flynn helped me
to understand. And *actually* adds Anna's Acked-by.
Before this v3 fixed three bugs found by Sashiko - all
error-path related.
1/ patch 5 - make sure d_drop() gets called in all error cases
2/ patch 8 - dput() sdentry on all error paths
3/ patch 9 - check error return from d_duplicate() correctly.
Original intro:
I am working on changes to directory locking. The medium term goal is
to lift d_alloc_parallel() locking above i_rwsem, so d_alloc_paralle()
can be run unlocked, and consequently cannot be called when ->i_rwsem is
held.
The requires:
1/ the d_alloc_parallel() not be called while i_rwsem is held. NFS does
this in a few places. New interfaces d_alloc_trylock() and d_duplicate()
are now available and can be used instead.
2/ a dentry must not be d_drop()ed while an operation is ongoing, as an
unlocked d_alloc_parallel() would then be able to create a new
dentry with the same name. In particular d_rehash() must be phased
out as it won't be safe. Various techiniques are used to avoid the
need for d_drop(), particularly that d_splice_alias() is now able to
splice a hashed alias.
Further, the first two patches here fixes bugs that I found while
developing and reviewing these changes.
[PATCH v4 1/9] nfs: fix open-blocking with d_fsdata
[PATCH v4 2/9] nfs: fix nfs_call_unlink()
[PATCH v4 3/9] nfs: remove d_drop()/d_alloc_parallel() from
[PATCH v4 4/9] nfs: use d_splice_alias() in nfs_link()
[PATCH v4 5/9] nfs: don't d_drop() before d_splice_alias()
[PATCH v4 6/9] nfs: don't d_drop() before d_splice_alias() in
[PATCH v4 7/9] nfs: Use d_alloc_trylock() in nfs_prime_dcache()
[PATCH v4 8/9] nfs: use d_alloc_trylock() in silly-rename
[PATCH v4 9/9] nfs: use d_duplicate()
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v4 1/9] nfs: fix open-blocking with d_fsdata
2026-10-28 4:06 [PATCH v4 0/9] nfs: prepare for locking changes NeilBrown
@ 2026-10-28 4:06 ` NeilBrown
2026-10-28 4:06 ` [PATCH v4 2/9] nfs: fix nfs_call_unlink() NeilBrown
` (8 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: NeilBrown @ 2026-10-28 4:06 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Alexander Viro,
Christian Brauner, Jonathan Flynn
Cc: Jeff Layton, Jan Kara, linux-fsdevel, linux-nfs, linux-kernel
From: NeilBrown <neil@brown.name>
The logic described in block_revalidate() that ->d_lock must be taken in
lookup_open() missed the fact that sometimes lookup_open() isn't called.
If open_last_lookups() is called while still in RCU-walk and
lookup_fast() finds a dentry, it does not take ->d_lock or increment
d_count() before calling d_revalidate() - only afterwards.
So on that path d_count() can be incremented and the file opened *after*
nfs_unlink() has checked d_count() and set NFS_FSDATA_BLOCKED. So an
application could have the file open when it is unlinked on the server.
To fix this we need block_revalidate() to ensure the RCU-walk path
is blocked as well. This is easily done with
write_seqcount_invalidate(&dentry->d_seq);
legitimize_path() will increment d_count() and then check d_seq. The
write_seqcount_invalidate() call will cause that check to fail if
nfs_unlink() has already seen d_count() being only 1. So the RCU-walk
will fail with -ECHILD and it will be retried as a ref-walk.
This requires that ->d_fsdata is loaded *after* read_seqcount_begin()
loaded ->d_seq, so the smp_load_acquire() in __nfs_lookup_revalidate()
is important.
Acked-by: Anna Schumaker <anna.schumaker@hammerspace.com>
Co-developed-by: Jonathan Flynn <jonathan.flynn@hammerspace.com>
Signed-off-by: Jonathan Flynn <jonathan.flynn@hammerspace.com>
Fixes: 99bc9f2eb3f7 ("NFS: add barriers when testing for NFS_FSDATA_BLOCKED")
Signed-off-by: NeilBrown <neil@brown.name>
---
fs/nfs/dir.c | 30 ++++++++++++++++++++++++------
1 file changed, 24 insertions(+), 6 deletions(-)
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index 354f986e60c4..cde963e2bcb8 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -1835,7 +1835,11 @@ __nfs_lookup_revalidate(struct dentry *dentry, unsigned int flags)
if (dentry->d_fsdata == NFS_FSDATA_BLOCKED)
return -ECHILD;
} else {
- /* Wait for unlink to complete - see unblock_revalidate() */
+ /* Wait for unlink to complete - see unblock_revalidate().
+ * smp_load_acquire() ensures ->d_fsdata is read after
+ * ->d_seq was read, matching block_revalidate() which
+ * sets ->d_fsdata before ->d_seq.
+ */
wait_var_event(&dentry->d_fsdata,
smp_load_acquire(&dentry->d_fsdata)
!= NFS_FSDATA_BLOCKED);
@@ -1856,15 +1860,29 @@ static void block_revalidate(struct dentry *dentry)
/* old devname - just in case */
kfree(dentry->d_fsdata);
+ dentry->d_fsdata = NFS_FSDATA_BLOCKED;
+
/* Any new reference that could lead to an open
- * will take ->d_lock in lookup_open() -> d_lookup().
- * Holding this lock ensures we cannot race with
- * __nfs_lookup_revalidate() and removes and need
- * for further barriers.
+ * will either:
+ * - take ->d_lock in lookup_open() -> d_lookup() or
+ * - will check d_seq in legitimize_path() or similar
+ * after taking the reference.
+ *
+ * Holding this lock and invalidating ->d_seq *after* recording
+ * NFS_FSDATA_BLOCKED ensures we cannot race with
+ * __nfs_lookup_revalidate().
+ * If __nfs_lookup_revalidate() doesn't see NFS_FSDATA_BLOCKED then:
+ * In the ref-walk case - d_lookup() will have incremented
+ * d_count() before we check it.
+ * In the RCU-walk case - ->d_seq must have been sampled before
+ * we call write_seqcount_invalidate() below (because it was sampled
+ * before we set NFS_FSDATA_BLOCKED) so d_count() will have been
+ * incremented or the d_seq check will fail in legitimize_path()
+ * (or both).
*/
lockdep_assert_held(&dentry->d_lock);
+ write_seqcount_invalidate(&dentry->d_seq);
- dentry->d_fsdata = NFS_FSDATA_BLOCKED;
}
static void unblock_revalidate(struct dentry *dentry)
base-commit: 864c07de0c7dcbca58b61625dfa2e461ae63b242
--
2.50.0.107.gf914562f5916.dirty
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v4 2/9] nfs: fix nfs_call_unlink()
2026-10-28 4:06 [PATCH v4 0/9] nfs: prepare for locking changes NeilBrown
2026-10-28 4:06 ` [PATCH v4 1/9] nfs: fix open-blocking with d_fsdata NeilBrown
@ 2026-10-28 4:06 ` NeilBrown
2026-10-28 4:06 ` [PATCH v4 3/9] nfs: remove d_drop()/d_alloc_parallel() from nfs_atomic_open() NeilBrown
` (7 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: NeilBrown @ 2026-10-28 4:06 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Alexander Viro,
Christian Brauner, Jonathan Flynn
Cc: Jeff Layton, Jan Kara, linux-fsdevel, linux-nfs, linux-kernel
From: NeilBrown <neil@brown.name>
nfs_call_unlink() calls d_alloc_parallel() on a dentry which has not had
the d_hash calculated. d_alloc_parallel() assumes this is calculated and
doesn't calculate it itself.
So this will never find an existing dentry and so will not detect
the races it aims to detect.
(This dentry ("alias") is never hashed so there is no lasting
inconsistency in the dcache.)
Fix this by calling full_name_hash()
Acked-by: Anna Schumaker <anna.schumaker@hammerspace.com>
Fixes: 565277f63c61 ("NFS: Fix a race in sillyrename")
Signed-off-by: NeilBrown <neil@brown.name>
---
fs/nfs/unlink.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/fs/nfs/unlink.c b/fs/nfs/unlink.c
index c8d712204e64..11a46a993e11 100644
--- a/fs/nfs/unlink.c
+++ b/fs/nfs/unlink.c
@@ -125,6 +125,9 @@ static int nfs_call_unlink(struct dentry *dentry, struct inode *inode, struct nf
struct dentry *alias;
down_read_non_owner(&NFS_I(dir)->rmdir_sem);
+ data->args.name.hash = full_name_hash(dentry->d_parent,
+ data->args.name.name,
+ data->args.name.len);
alias = d_alloc_parallel(dentry->d_parent, &data->args.name);
if (IS_ERR(alias)) {
up_read_non_owner(&NFS_I(dir)->rmdir_sem);
--
2.50.0.107.gf914562f5916.dirty
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v4 3/9] nfs: remove d_drop()/d_alloc_parallel() from nfs_atomic_open()
2026-10-28 4:06 [PATCH v4 0/9] nfs: prepare for locking changes NeilBrown
2026-10-28 4:06 ` [PATCH v4 1/9] nfs: fix open-blocking with d_fsdata NeilBrown
2026-10-28 4:06 ` [PATCH v4 2/9] nfs: fix nfs_call_unlink() NeilBrown
@ 2026-10-28 4:06 ` NeilBrown
2026-10-28 4:06 ` [PATCH v4 4/9] nfs: use d_splice_alias() in nfs_link() NeilBrown
` (6 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: NeilBrown @ 2026-10-28 4:06 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Alexander Viro,
Christian Brauner, Jonathan Flynn
Cc: Jeff Layton, Jan Kara, linux-fsdevel, linux-nfs, linux-kernel
From: NeilBrown <neil@brown.name>
It is important that two non-create NFS "open"s of a negative dentry
don't race. They both have only a shared lock on i_rwsem and so could
run concurrently, but they might both try to call d_splice_alias() at
the same time which is confusing at best.
nfs_atomic_open() currently avoids this by discarding the negative
dentry and creating a new one using d_alloc_parallel(). Only one thread
can successfully get the d_in_lookup() dentry, the other will wait for
the first to finish, and can use the result of that first lookup.
A proposed locking change inverts the order between i_rwsem and
d_alloc_parallel() so it will not be safe to call d_alloc_parallel()
while holding i_rwsem - even shared.
We can achieve the same effect by causing ->d_revalidate to invalidate a
negative dentry when LOOKUP_OPEN is set. Doing this is consistent with
the "close to open" caching semantics of NFS which requires the server
to be queried whenever opening a file - cached information must not be
trusted.
With this change to ->d_revaliate (implemented in nfs_neg_need_reval())
we can be sure that we have exclusive access to any dentry that reaches
nfs_atomic_open(). Either O_CREAT was requested and so the parent is
locked exclusively, or the dentry will have DCACHE_PAR_LOOKUP set.
[Note that when nfs_neg_need_reval() returns 1, ->d_revalidate returns 0
which triggers invalidation].
This means that the d_drop() and d_alloc_parallel() calls in
nfs_atomic_lookup() are no longer needed to provide exclusion
There is still a d_drop() which allows d_splice_alias() to succeed.
This will be removed in a future patch.
Acked-by: Anna Schumaker <anna.schumaker@hammerspace.com>
Signed-off-by: NeilBrown <neil@brown.name>
---
fs/nfs/dir.c | 30 +++++++-----------------------
1 file changed, 7 insertions(+), 23 deletions(-)
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index cde963e2bcb8..cf366329666f 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -1656,6 +1656,13 @@ int nfs_neg_need_reval(struct inode *dir, struct dentry *dentry,
{
if (flags & (LOOKUP_CREATE | LOOKUP_RENAME_TARGET))
return 0;
+ if (flags & LOOKUP_OPEN)
+ /* close-to-open semantics require we go to the server
+ * on each open. By invalidating the dentry we
+ * also ensure nfs_atomic_open() always has exclusive
+ * access to the dentry.
+ */
+ return 1;
if (NFS_SERVER(dir)->flags & NFS_MOUNT_LOOKUP_CACHE_NONEG)
return 1;
/* Case insensitive server? Revalidate negative dentries */
@@ -2129,7 +2136,6 @@ int nfs_atomic_open(struct inode *dir, struct dentry *dentry,
struct inode *inode;
unsigned int lookup_flags = 0;
unsigned long dir_verifier;
- bool switched = false;
int created = 0;
int err;
@@ -2174,17 +2180,6 @@ int nfs_atomic_open(struct inode *dir, struct dentry *dentry,
attr.ia_size = 0;
}
- if (!(open_flags & O_CREAT) && !d_in_lookup(dentry)) {
- d_drop(dentry);
- switched = true;
- dentry = d_alloc_parallel(dentry->d_parent,
- &dentry->d_name);
- if (IS_ERR(dentry))
- return PTR_ERR(dentry);
- if (unlikely(!d_in_lookup(dentry)))
- return finish_no_open(file, dentry);
- }
-
ctx = create_nfs_open_context(dentry, open_flags, file);
err = PTR_ERR(ctx);
if (IS_ERR(ctx))
@@ -2235,10 +2230,6 @@ int nfs_atomic_open(struct inode *dir, struct dentry *dentry,
trace_nfs_atomic_open_exit(dir, ctx, open_flags, err);
put_nfs_open_context(ctx);
out:
- if (unlikely(switched)) {
- d_lookup_done(dentry);
- dput(dentry);
- }
return err;
no_open:
@@ -2261,13 +2252,6 @@ int nfs_atomic_open(struct inode *dir, struct dentry *dentry,
res = ERR_PTR(-EOPENSTALE);
}
}
- if (switched) {
- d_lookup_done(dentry);
- if (!res)
- res = dentry;
- else
- dput(dentry);
- }
return finish_no_open(file, res);
}
EXPORT_SYMBOL_GPL(nfs_atomic_open);
--
2.50.0.107.gf914562f5916.dirty
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v4 4/9] nfs: use d_splice_alias() in nfs_link()
2026-10-28 4:06 [PATCH v4 0/9] nfs: prepare for locking changes NeilBrown
` (2 preceding siblings ...)
2026-10-28 4:06 ` [PATCH v4 3/9] nfs: remove d_drop()/d_alloc_parallel() from nfs_atomic_open() NeilBrown
@ 2026-10-28 4:06 ` NeilBrown
2026-10-28 4:06 ` [PATCH v4 5/9] nfs: don't d_drop() before d_splice_alias() NeilBrown
` (5 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: NeilBrown @ 2026-10-28 4:06 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Alexander Viro,
Christian Brauner, Jonathan Flynn
Cc: Jeff Layton, Jan Kara, linux-fsdevel, linux-nfs, linux-kernel
From: NeilBrown <neil@brown.name>
When filename_linkat() calls filename_create() which ultimately
calls ->lookup, the flags LOOKUP_CREATE|LOOKUP_EXCL are passed.
nfs_lookup() treats this as an exclusive create (which it is) and
skips the ->lookup, leaving the dentry unchanged.
Currently that means nfs_link() can get a hashed dentry (if the name was
already in the cache) or an unhashed dentry (if it wasn't). As none of
d_add(), d_instantiate(), d_splice_alias() could handle both of these,
nfs_link() calls d_drop() and then d_add().
Recent changes to d_splice_alias() mean that it *can* work with either
hashed or unhashed dentries. Future changes to locking mean that it
will be unsafe to d_drop() a dentry while an operation (in this case
"link()") is still ongoing.
So change to use d_splice_alias(), and not to d_drop() until an error is
detected (as in that case was can't be sure what is actually on the server).
Also update the comment for nfs_is_exclusive_create() to note that
link(), mkdir(), mknod(), symlink() all appear as exclusive creates.
Those other than link() already used d_splice_alias() via
nfs_add_or_obtain().
Possibly nfs_is_exclusive_create() should return true even with NFSv2
when the request isn't an open (not LOOKUP_OPEN).
Acked-by: Anna Schumaker <anna.schumaker@hammerspace.com>
Signed-off-by: NeilBrown <neil@brown.name>
---
fs/nfs/dir.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index cf366329666f..e4c7acaaf2ba 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -1570,6 +1570,9 @@ static int nfs_check_verifier(struct inode *dir, struct dentry *dentry,
/*
* Use intent information to check whether or not we're going to do
* an O_EXCL create using this path component.
+ * Note that link(), mkdir(), mknod(), symlink() all appear as
+ * exclusive creation. Regular file creation could be distinguished
+ * with LOOKUP_OPEN.
*/
static int nfs_is_exclusive_create(struct inode *dir, unsigned int flags)
{
@@ -2715,14 +2718,15 @@ nfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry)
old_dentry, dentry);
trace_nfs_link_enter(inode, dir, dentry);
- d_drop(dentry);
if (S_ISREG(inode->i_mode))
nfs_sync_inode(inode);
error = NFS_PROTO(dir)->link(inode, dir, &dentry->d_name);
if (error == 0) {
nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
ihold(inode);
- d_add(dentry, inode);
+ d_splice_alias(inode, dentry);
+ } else {
+ d_drop(dentry);
}
trace_nfs_link_exit(inode, dir, dentry, error);
return error;
--
2.50.0.107.gf914562f5916.dirty
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v4 5/9] nfs: don't d_drop() before d_splice_alias()
2026-10-28 4:06 [PATCH v4 0/9] nfs: prepare for locking changes NeilBrown
` (3 preceding siblings ...)
2026-10-28 4:06 ` [PATCH v4 4/9] nfs: use d_splice_alias() in nfs_link() NeilBrown
@ 2026-10-28 4:06 ` NeilBrown
2026-10-28 4:06 ` [PATCH v4 6/9] nfs: don't d_drop() before d_splice_alias() in atomic_create NeilBrown
` (4 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: NeilBrown @ 2026-10-28 4:06 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Alexander Viro,
Christian Brauner, Jonathan Flynn
Cc: Jeff Layton, Jan Kara, linux-fsdevel, linux-nfs, linux-kernel
From: NeilBrown <neil@brown.name>
nfs_add_or_obtain() is used, often via nfs_instantiate(), to attach a
newly created inode to the appropriate dentry - or to provide an
alternate dentry.
It has to drop the dentry first, which is problematic for proposed
locking changes.
As d_splice_alias() now works with hashed dentries, the d_drop() is no
longer needed.
However we still d_drop() on error as the status of the name is
uncertain.
_nfs4_open_and_get_state() is only used for files so we should be able to
use d_instantiate(). However as that depends on the server for
correctness, it is safer to stay with the current code pattern and use
d_splice_alias() there too.
Acked-by: Anna Schumaker <anna.schumaker@hammerspace.com>
Signed-off-by: NeilBrown <neil@brown.name>
---
fs/nfs/dir.c | 4 ++--
fs/nfs/nfs4proc.c | 1 -
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index e4c7acaaf2ba..b29dc1260d91 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -2362,8 +2362,6 @@ nfs_add_or_obtain(struct dentry *dentry, struct nfs_fh *fhandle,
struct dentry *d;
int error;
- d_drop(dentry);
-
if (fhandle->size == 0) {
error = NFS_PROTO(dir)->lookup(dir, dentry, &dentry->d_name,
fhandle, fattr);
@@ -2381,6 +2379,8 @@ nfs_add_or_obtain(struct dentry *dentry, struct nfs_fh *fhandle,
inode = nfs_fhget(dentry->d_sb, fhandle, fattr);
d = d_splice_alias(inode, dentry);
out:
+ if (IS_ERR(d))
+ d_drop(dentry);
dput(parent);
return d;
out_error:
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index 21e5df7e5251..e8ca92b07190 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -3113,7 +3113,6 @@ static int _nfs4_open_and_get_state(struct nfs4_opendata *opendata,
nfs_set_verifier(dentry, dir_verifier);
if (d_really_is_negative(dentry)) {
struct dentry *alias;
- d_drop(dentry);
alias = d_splice_alias(igrab(state->inode), dentry);
/* d_splice_alias() can't fail here - it's a non-directory */
if (alias) {
--
2.50.0.107.gf914562f5916.dirty
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v4 6/9] nfs: don't d_drop() before d_splice_alias() in atomic_create.
2026-10-28 4:06 [PATCH v4 0/9] nfs: prepare for locking changes NeilBrown
` (4 preceding siblings ...)
2026-10-28 4:06 ` [PATCH v4 5/9] nfs: don't d_drop() before d_splice_alias() NeilBrown
@ 2026-10-28 4:06 ` NeilBrown
2026-10-28 4:06 ` [PATCH v4 7/9] nfs: Use d_alloc_trylock() in nfs_prime_dcache() NeilBrown
` (3 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: NeilBrown @ 2026-10-28 4:06 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Alexander Viro,
Christian Brauner, Jonathan Flynn
Cc: Jeff Layton, Jan Kara, linux-fsdevel, linux-nfs, linux-kernel
From: NeilBrown <neil@brown.name>
When atomic_create fails with -ENOENT we currently d_drop() the dentry
and then re-add it (d_splice_alias()) with a NULL inode.
This drop-and-re-add will not work with proposed locking changes.
As d_splice_alias() now supports hashed dentries, we don't need the
d_drop() until it is determined that some other error has occurred.
Acked-by: Anna Schumaker <anna.schumaker@hammerspace.com>
Signed-off-by: NeilBrown <neil@brown.name>
---
fs/nfs/dir.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index b29dc1260d91..b296d90c8e14 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -2196,7 +2196,6 @@ int nfs_atomic_open(struct inode *dir, struct dentry *dentry,
err = PTR_ERR(inode);
trace_nfs_atomic_open_exit(dir, ctx, open_flags, err);
put_nfs_open_context(ctx);
- d_drop(dentry);
switch (err) {
case -ENOENT:
if (nfs_server_capable(dir, NFS_CAP_CASE_INSENSITIVE))
@@ -2205,7 +2204,7 @@ int nfs_atomic_open(struct inode *dir, struct dentry *dentry,
dir_verifier = nfs_save_change_attribute(dir);
nfs_set_verifier(dentry, dir_verifier);
d_splice_alias(NULL, dentry);
- break;
+ goto out;
case -EISDIR:
case -ENOTDIR:
if (open_flags & __O_REGULAR) {
@@ -2221,6 +2220,7 @@ int nfs_atomic_open(struct inode *dir, struct dentry *dentry,
default:
break;
}
+ d_drop(dentry);
goto out;
}
file->f_mode |= FMODE_CAN_ODIRECT;
@@ -2254,7 +2254,8 @@ int nfs_atomic_open(struct inode *dir, struct dentry *dentry,
dput(res);
res = ERR_PTR(-EOPENSTALE);
}
- }
+ } else
+ d_drop(dentry);
return finish_no_open(file, res);
}
EXPORT_SYMBOL_GPL(nfs_atomic_open);
--
2.50.0.107.gf914562f5916.dirty
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v4 7/9] nfs: Use d_alloc_trylock() in nfs_prime_dcache()
2026-10-28 4:06 [PATCH v4 0/9] nfs: prepare for locking changes NeilBrown
` (5 preceding siblings ...)
2026-10-28 4:06 ` [PATCH v4 6/9] nfs: don't d_drop() before d_splice_alias() in atomic_create NeilBrown
@ 2026-10-28 4:06 ` NeilBrown
2026-10-28 4:06 ` [PATCH v4 8/9] nfs: use d_alloc_trylock() in silly-rename NeilBrown
` (2 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: NeilBrown @ 2026-10-28 4:06 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Alexander Viro,
Christian Brauner, Jonathan Flynn
Cc: Jeff Layton, Jan Kara, linux-fsdevel, linux-nfs, linux-kernel
From: NeilBrown <neil@brown.name>
NFS uses the results of readdir to prime the dcache. Using
d_alloc_parallel() can block if there is a concurrent lookup. Blocking
in that case is pointless as the lookup will add info to the dcache and
there is no value in the readdir waiting to see if it should add the
info too.
Also this call to d_alloc_parallel() is made while the parent
directory is locked. A proposed change to locking will lock the parent
later, after d_alloc_parallel(). This means it won't be safe to wait in
d_alloc_parallel() while holding the directory lock.
So change to use d_alloc_trylock(), which removes the need for
calculating the hash or doing a preliminary lookup.
Acked-by: Anna Schumaker <anna.schumaker@hammerspace.com>
Signed-off-by: NeilBrown <neil@brown.name>
---
fs/nfs/dir.c | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index b296d90c8e14..4ceab6209e4a 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -749,15 +749,12 @@ void nfs_prime_dcache(struct dentry *parent, struct nfs_entry *entry,
if (filename.len == 2 && filename.name[1] == '.')
return;
}
- filename.hash = full_name_hash(parent, filename.name, filename.len);
- dentry = d_lookup(parent, &filename);
again:
- if (!dentry) {
- dentry = d_alloc_parallel(parent, &filename);
- if (IS_ERR(dentry))
- return;
- }
+ dentry = d_alloc_trylock(parent, &filename);
+ if (IS_ERR(dentry))
+ return;
+
if (!d_in_lookup(dentry)) {
/* Is there a mountpoint here? If so, just exit */
if (!nfs_fsid_equal(&NFS_SB(dentry->d_sb)->fsid,
--
2.50.0.107.gf914562f5916.dirty
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v4 8/9] nfs: use d_alloc_trylock() in silly-rename
2026-10-28 4:06 [PATCH v4 0/9] nfs: prepare for locking changes NeilBrown
` (6 preceding siblings ...)
2026-10-28 4:06 ` [PATCH v4 7/9] nfs: Use d_alloc_trylock() in nfs_prime_dcache() NeilBrown
@ 2026-10-28 4:06 ` NeilBrown
2026-10-28 4:06 ` [PATCH v4 9/9] nfs: use d_duplicate() NeilBrown
2026-10-28 17:21 ` [PATCH v4 0/9] nfs: prepare for locking changes Anna Schumaker
9 siblings, 0 replies; 11+ messages in thread
From: NeilBrown @ 2026-10-28 4:06 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Alexander Viro,
Christian Brauner, Jonathan Flynn
Cc: Jeff Layton, Jan Kara, linux-fsdevel, linux-nfs, linux-kernel
From: NeilBrown <neil@brown.name>
Rather than performing a normal lookup (which will be awkward with
future locking changes) use d_alloc_trylock() to find a dentry for an
unused name, and then open-code the rest of lookup_slow() to see if it
is free on the server.
Acked-by: Anna Schumaker <anna.schumaker@hammerspace.com>
Signed-off-by: NeilBrown <neil@brown.name>
---
fs/nfs/unlink.c | 59 ++++++++++++++++++++++++++++++++-----------------
1 file changed, 39 insertions(+), 20 deletions(-)
diff --git a/fs/nfs/unlink.c b/fs/nfs/unlink.c
index 11a46a993e11..0eddc7ee1115 100644
--- a/fs/nfs/unlink.c
+++ b/fs/nfs/unlink.c
@@ -451,7 +451,7 @@ nfs_sillyrename(struct inode *dir, struct dentry *dentry)
static unsigned int sillycounter;
unsigned char silly[SILLYNAME_LEN + 1];
unsigned long long fileid;
- struct dentry *sdentry;
+ struct dentry *sdentry, *old;
struct inode *inode = d_inode(dentry);
struct rpc_task *task;
int error = -EBUSY;
@@ -468,26 +468,45 @@ nfs_sillyrename(struct inode *dir, struct dentry *dentry)
fileid = d_inode(dentry)->i_ino;
- sdentry = NULL;
- do {
+newname:
+ sillycounter++;
+ scnprintf(silly, sizeof(silly),
+ SILLYNAME_PREFIX "%0*llx%0*x",
+ SILLYNAME_FILEID_LEN, fileid,
+ SILLYNAME_COUNTER_LEN, sillycounter);
+
+ dfprintk(VFS, "NFS: trying to rename %pd to %s\n", dentry, silly);
+ sdentry = d_alloc_trylock(dentry->d_parent, &QSTR(silly));
+ if (sdentry == ERR_PTR(-EWOULDBLOCK))
+ /* Name currently being looked up */
+ goto newname;
+ if (IS_ERR(sdentry))
+ goto out;
+ if (!d_in_lookup(sdentry)) {
+ if (d_really_is_negative(sdentry)) {
+ /* try to get an in-lookup dentry */
+ d_drop(sdentry);
+ sillycounter--;
+ }
dput(sdentry);
- sillycounter++;
- scnprintf(silly, sizeof(silly),
- SILLYNAME_PREFIX "%0*llx%0*x",
- SILLYNAME_FILEID_LEN, fileid,
- SILLYNAME_COUNTER_LEN, sillycounter);
-
- dfprintk(VFS, "NFS: trying to rename %pd to %s\n",
- dentry, silly);
-
- sdentry = lookup_noperm(&QSTR(silly), dentry->d_parent);
- /*
- * N.B. Better to return EBUSY here ... it could be
- * dangerous to delete the file while it's in use.
- */
- if (IS_ERR(sdentry))
- goto out;
- } while (d_inode(sdentry) != NULL); /* need negative lookup */
+ goto newname;
+ }
+ /* This name isn't known locally - check on server */
+ old = nfs_lookup(dir, sdentry, 0);
+ d_lookup_done(sdentry);
+ /*
+ * N.B. Better to return EBUSY here ... it could be
+ * dangerous to delete the file while it's in use.
+ */
+ if (IS_ERR(old)) {
+ dput(sdentry);
+ goto out;
+ }
+ if (old || d_is_positive(sdentry)) {
+ dput(old);
+ dput(sdentry);
+ goto newname;
+ }
ihold(inode);
--
2.50.0.107.gf914562f5916.dirty
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v4 9/9] nfs: use d_duplicate()
2026-10-28 4:06 [PATCH v4 0/9] nfs: prepare for locking changes NeilBrown
` (7 preceding siblings ...)
2026-10-28 4:06 ` [PATCH v4 8/9] nfs: use d_alloc_trylock() in silly-rename NeilBrown
@ 2026-10-28 4:06 ` NeilBrown
2026-10-28 17:21 ` [PATCH v4 0/9] nfs: prepare for locking changes Anna Schumaker
9 siblings, 0 replies; 11+ messages in thread
From: NeilBrown @ 2026-10-28 4:06 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Alexander Viro,
Christian Brauner, Jonathan Flynn
Cc: Jeff Layton, Jan Kara, linux-fsdevel, linux-nfs, linux-kernel
From: NeilBrown <neil@brown.name>
As preparation for d_alloc_parallel() being allowed without the
directory locked, use d_duplicate() to duplicate a dentry for silly
rename.
This results in an in-lookup name, so d_lookup_done() must be called.
Acked-by: Anna Schumaker <anna.schumaker@hammerspace.com>
Signed-off-by: NeilBrown <neil@brown.name>
---
fs/nfs/dir.c | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index 4ceab6209e4a..1a5f64ed26b6 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -2819,11 +2819,12 @@ int nfs_rename(const struct mnt_idmap *idmap, struct inode *old_dir,
spin_unlock(&new_dentry->d_lock);
/* copy the target dentry's name */
- dentry = d_alloc(new_dentry->d_parent,
- &new_dentry->d_name);
- if (!dentry)
+ dentry = d_duplicate(new_dentry);
+ if (IS_ERR(dentry)) {
+ /* Keep using -ETXTBSY error */
+ dentry = NULL;
goto out;
-
+ }
/* silly-rename the existing target ... */
err = nfs_sillyrename(new_dir, new_dentry);
if (err)
@@ -2888,8 +2889,10 @@ int nfs_rename(const struct mnt_idmap *idmap, struct inode *old_dir,
nfs_dentry_handle_enoent(old_dentry);
/* new dentry created? */
- if (dentry)
+ if (dentry) {
+ d_lookup_done(dentry);
dput(dentry);
+ }
return error;
}
EXPORT_SYMBOL_GPL(nfs_rename);
--
2.50.0.107.gf914562f5916.dirty
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v4 0/9] nfs: prepare for locking changes.
2026-10-28 4:06 [PATCH v4 0/9] nfs: prepare for locking changes NeilBrown
` (8 preceding siblings ...)
2026-10-28 4:06 ` [PATCH v4 9/9] nfs: use d_duplicate() NeilBrown
@ 2026-10-28 17:21 ` Anna Schumaker
9 siblings, 0 replies; 11+ messages in thread
From: Anna Schumaker @ 2026-10-28 17:21 UTC (permalink / raw)
To: NeilBrown, Trond Myklebust, Alexander Viro, Christian Brauner,
Jonathan Flynn
Cc: Jeff Layton, Jan Kara, linux-fsdevel, linux-nfs, linux-kernel
Hi Neil,
On Tue, Oct 27, 2026, at 9:06 PM, NeilBrown wrote:
> Now that the requirement for this are upstream this could go via the NFS
> tree or the VFS tree.
Sounds good! I think we should plan to take this through the NFS tree.
Thanks,
Anna
>
> Thanks,
> NeilBrown
>
>
> This v4 fixes the problems with patch 1 which Jonathan Flynn helped me
> to understand. And *actually* adds Anna's Acked-by.
>
> Before this v3 fixed three bugs found by Sashiko - all
> error-path related.
>
> 1/ patch 5 - make sure d_drop() gets called in all error cases
> 2/ patch 8 - dput() sdentry on all error paths
> 3/ patch 9 - check error return from d_duplicate() correctly.
>
> Original intro:
> I am working on changes to directory locking. The medium term goal is
> to lift d_alloc_parallel() locking above i_rwsem, so d_alloc_paralle()
> can be run unlocked, and consequently cannot be called when ->i_rwsem is
> held.
>
> The requires:
> 1/ the d_alloc_parallel() not be called while i_rwsem is held. NFS does
> this in a few places. New interfaces d_alloc_trylock() and d_duplicate()
> are now available and can be used instead.
>
> 2/ a dentry must not be d_drop()ed while an operation is ongoing, as an
> unlocked d_alloc_parallel() would then be able to create a new
> dentry with the same name. In particular d_rehash() must be phased
> out as it won't be safe. Various techiniques are used to avoid the
> need for d_drop(), particularly that d_splice_alias() is now able to
> splice a hashed alias.
>
> Further, the first two patches here fixes bugs that I found while
> developing and reviewing these changes.
>
>
> [PATCH v4 1/9] nfs: fix open-blocking with d_fsdata
> [PATCH v4 2/9] nfs: fix nfs_call_unlink()
> [PATCH v4 3/9] nfs: remove d_drop()/d_alloc_parallel() from
> [PATCH v4 4/9] nfs: use d_splice_alias() in nfs_link()
> [PATCH v4 5/9] nfs: don't d_drop() before d_splice_alias()
> [PATCH v4 6/9] nfs: don't d_drop() before d_splice_alias() in
> [PATCH v4 7/9] nfs: Use d_alloc_trylock() in nfs_prime_dcache()
> [PATCH v4 8/9] nfs: use d_alloc_trylock() in silly-rename
> [PATCH v4 9/9] nfs: use d_duplicate()
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-10-28 17:22 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-28 4:06 [PATCH v4 0/9] nfs: prepare for locking changes NeilBrown
2026-10-28 4:06 ` [PATCH v4 1/9] nfs: fix open-blocking with d_fsdata NeilBrown
2026-10-28 4:06 ` [PATCH v4 2/9] nfs: fix nfs_call_unlink() NeilBrown
2026-10-28 4:06 ` [PATCH v4 3/9] nfs: remove d_drop()/d_alloc_parallel() from nfs_atomic_open() NeilBrown
2026-10-28 4:06 ` [PATCH v4 4/9] nfs: use d_splice_alias() in nfs_link() NeilBrown
2026-10-28 4:06 ` [PATCH v4 5/9] nfs: don't d_drop() before d_splice_alias() NeilBrown
2026-10-28 4:06 ` [PATCH v4 6/9] nfs: don't d_drop() before d_splice_alias() in atomic_create NeilBrown
2026-10-28 4:06 ` [PATCH v4 7/9] nfs: Use d_alloc_trylock() in nfs_prime_dcache() NeilBrown
2026-10-28 4:06 ` [PATCH v4 8/9] nfs: use d_alloc_trylock() in silly-rename NeilBrown
2026-10-28 4:06 ` [PATCH v4 9/9] nfs: use d_duplicate() NeilBrown
2026-10-28 17:21 ` [PATCH v4 0/9] nfs: prepare for locking changes Anna Schumaker
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®