From: Jonathan Flynn <jonathan.flynn@hammerspace.com>
To: NeilBrown <neil@brown.name>,
Alexander Viro <viro@zeniv.linux.org.uk>,
Christian Brauner <brauner@kernel.org>,
Trond Myklebust <trondmy@kernel.org>,
Anna Schumaker <anna@kernel.org>
Cc: Jeff Layton <jlayton@kernel.org>, Chuck Lever <cel@kernel.org>,
Jan Kara <jack@suse.cz>, Jonathan Corbet <corbet@lwn.net>,
linux-doc@vger.kernel.org, linux-nfs@vger.kernel.org,
linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH RFC 11/12] nfs: look a name up again once its removal has unhashed it
Date: Thu, 29 Oct 2026 12:09:56 -0600 [thread overview]
Message-ID: <20261029180957.387138-12-jonathan.flynn@hammerspace.com> (raw)
In-Reply-To: <20261029180957.387138-1-jonathan.flynn@hammerspace.com>
An open or lookup of a name can wait for a removal of the same name made
with the directory locked shared, and is then ordered after it. When
that removal succeeds while the waiter still holds the name's cache
entry, the entry is only taken out of the cache, not emptied, so the
waiter is left holding an entry for a name that no longer exists.
Revalidation can then accept it: an open without O_EXCL is sent to the
server for the removed file, and if the client holds a delegation for
the directory, an exclusive create of the name fails with "file exists".
When NFSv4's revalidation has waited for such a removal and finds the
entry out of the cache once the wait ends, have it answer that the entry
is invalid, so that the name is looked up again;
__nfs_lookup_revalidate() now reports whether the mark it waited for was
the shared one. Without such a wait, an entry out of the cache goes
through the usual checks, as before. Its holder may be unable to look
the name up again: overlayfs revalidates the entries of its lower layers
itself, and an answer of "invalid" for one that a transient error took
out of the cache would leave the overlay reporting it stale until it
dropped its own entry. A lookup that took the entry while the removal
was in flight but finds the mark already clear is not covered either;
it overlapped the removal, so the old entry is still an answer it may
be given.
This relies on the VFS taking the entry out of the cache before it calls
->unlink_shared_done(), which clears the mark, and on the wait reading
the mark with acquire ordering, which pairs with the release in the
clear: a lookup that finds the mark clear, whether it slept or not, then
also sees the entry out of the cache. A removal made with the directory
locked exclusive, and a rename onto the name, can clear the mark before
the entry is taken out of the cache; say so at the wait.
Signed-off-by: Jonathan Flynn <jonathan.flynn@hammerspace.com>
---
fs/nfs/dir.c | 50 +++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 43 insertions(+), 7 deletions(-)
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index 0b6de87030..1f255001a1 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -1889,6 +1889,11 @@ nfs_do_lookup_revalidate(struct inode *dir, const struct qstr *name,
return nfs_lookup_revalidate_done(dir, dentry, inode, error);
}
+/*
+ * Answers -ECHILD under LOOKUP_RCU while a removal of the name is in
+ * flight. Otherwise waits for any such removal to end, and answers 1 if
+ * the one it found was made with the parent held shared, else 0.
+ */
static int
__nfs_lookup_revalidate(struct dentry *dentry, unsigned int flags)
{
@@ -1898,15 +1903,28 @@ __nfs_lookup_revalidate(struct dentry *dentry, unsigned int flags)
return -ECHILD;
} else {
void **fsdata = &dentry->d_fsdata;
+ void *mark;
- /* 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 for unlink to complete - see unblock_revalidate(). One
+ * load, as there are two values to test. smp_load_acquire()
+ * ensures ->d_fsdata is read after ->d_seq was read, matching
+ * __block_revalidate(), which sets ->d_fsdata before ->d_seq.
+ * The acquire in the wait pairs with the release in
+ * unblock_revalidate(): a waiter that finds the mark clear
+ * sees what the remover did before clearing it. For a removal
+ * made with the parent held shared, that includes the unhash of
+ * a dentry it took away, as the VFS calls d_delete() before
+ * ->unlink_shared_done(); nfs_unlink() and nfs_rename() may
+ * clear the mark first and unhash after.
*/
+ mark = smp_load_acquire(fsdata);
+ if (!nfs_fsdata_blocked(mark))
+ return 0;
wait_var_event(fsdata,
- /* one load: there are two values to test */
+ /* acquire: pairs with unblock_revalidate() */
!nfs_fsdata_blocked(smp_load_acquire(fsdata)));
+ return nfs_fsdata_blocked_shared(mark);
}
return 0;
}
@@ -1914,7 +1932,7 @@ __nfs_lookup_revalidate(struct dentry *dentry, unsigned int flags)
static int nfs_lookup_revalidate(struct inode *dir, const struct qstr *name,
struct dentry *dentry, unsigned int flags)
{
- if (__nfs_lookup_revalidate(dentry, flags))
+ if (__nfs_lookup_revalidate(dentry, flags) < 0)
return -ECHILD;
return nfs_do_lookup_revalidate(dir, name, dentry, flags);
}
@@ -2329,10 +2347,28 @@ nfs4_lookup_revalidate(struct inode *dir, const struct qstr *name,
struct dentry *dentry, unsigned int flags)
{
struct inode *inode;
+ int ret;
- if (__nfs_lookup_revalidate(dentry, flags))
+ ret = __nfs_lookup_revalidate(dentry, flags);
+ if (ret < 0)
return -ECHILD;
+ /*
+ * A removal made with the parent held shared that succeeded while
+ * this lookup waited for it, holding the dentry, has left the dentry
+ * positive but unhashed: d_delete() empties a dentry only for its last
+ * user. The dentry no longer stands for its name, so have the caller
+ * look the name up again; the checks below could accept it, since
+ * with a directory delegation the removal stamped it delegated. Only
+ * ref-walk waits, so this never answers 0 under LOOKUP_RCU. A dentry
+ * found unhashed without such a wait is left to the checks below, as
+ * before: its holder may be unable to look the name up again, as
+ * overlayfs is for the dentries of its lower layers, which it
+ * revalidates itself.
+ */
+ if (ret && d_unhashed(dentry))
+ return 0;
+
trace_nfs_lookup_revalidate_enter(dir, dentry, flags);
if (!(flags & LOOKUP_OPEN) || (flags & LOOKUP_DIRECTORY))
--
2.43.0
next prev parent reply other threads:[~2026-10-29 18:10 UTC|newest]
Thread overview: 31+ messages / expand[flat|nested] mbox.gz Atom feed top
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-29 18:09 ` [PATCH 0/3] nfs: fix the silly-delete hand-off that 2/9 makes reachable Jonathan Flynn
2026-10-29 18:09 ` [PATCH 1/3] nfs: do not transfer a sillydelete onto a blocked dentry Jonathan Flynn
2026-10-29 18:09 ` [PATCH 2/3] nfs: answer a handed-off silly-delete on a removal target with -EBUSY Jonathan Flynn
2026-10-29 18:09 ` [PATCH 3/3] nfs: take rmdir_sem for a silly-delete only when its REMOVE is sent Jonathan Flynn
2026-10-29 21:58 ` [PATCH 0/3] nfs: fix the silly-delete hand-off that 2/9 makes reachable 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
2026-10-29 18:09 ` [PATCH RFC 00/12] vfs, nfs: concurrent create and unlink in one directory Jonathan Flynn
2026-10-29 18:09 ` [PATCH RFC 01/12] nfs: update a server's capabilities in one step Jonathan Flynn
2026-10-29 18:09 ` [PATCH RFC 02/12] vfs: allow the parent lock to be shared for O_CREAT Jonathan Flynn
2026-10-29 18:09 ` [PATCH RFC 03/12] nfs: tell the VFS when two names can be one entry on the server Jonathan Flynn
2026-10-29 22:23 ` NeilBrown
2026-10-29 18:09 ` [PATCH RFC 04/12] nfs: concurrent O_CREAT in one NFSv4 directory Jonathan Flynn
2026-10-29 18:09 ` [PATCH RFC 05/12] vfs: factor file removal into a helper, no functional change Jonathan Flynn
2026-10-29 18:09 ` [PATCH RFC 06/12] vfs: let a filesystem remove files with the parent directory locked shared Jonathan Flynn
2026-10-29 18:09 ` [PATCH RFC 07/12] nfs: add a second mark for a removal made with the parent " Jonathan Flynn
2026-10-29 18:09 ` [PATCH RFC 08/12] nfs: keep the dentry of a shared-parent removal hashed Jonathan Flynn
2026-10-29 18:09 ` [PATCH RFC 09/12] nfs: do not cache a name from a listing that a removal overlapped Jonathan Flynn
2026-10-29 18:09 ` [PATCH RFC 10/12] nfs: add the removal method for a parent directory locked shared Jonathan Flynn
2026-10-29 18:09 ` Jonathan Flynn [this message]
2026-10-29 18:09 ` [PATCH RFC 12/12] nfs: remove files with the parent directory locked shared on NFSv4 Jonathan Flynn
2026-10-29 22:18 ` [PATCH RFC 00/12] vfs, nfs: concurrent create and unlink in one directory NeilBrown
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261029180957.387138-12-jonathan.flynn@hammerspace.com \
--to=jonathan.flynn@hammerspace.com \
--cc=anna@kernel.org \
--cc=brauner@kernel.org \
--cc=cel@kernel.org \
--cc=corbet@lwn.net \
--cc=jack@suse.cz \
--cc=jlayton@kernel.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-nfs@vger.kernel.org \
--cc=neil@brown.name \
--cc=trondmy@kernel.org \
--cc=viro@zeniv.linux.org.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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®