[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <166147984376.25420.10279547885121512294.stgit@noble.brown>
Date: Fri, 26 Aug 2022 12:10:43 +1000
From: NeilBrown <neilb@...e.de>
To: Al Viro <viro@...iv.linux.org.uk>,
Linus Torvalds <torvalds@...ux-foundation.org>,
Daire Byrne <daire@...g.com>,
Trond Myklebust <trond.myklebust@...merspace.com>,
Chuck Lever <chuck.lever@...cle.com>
Cc: Linux NFS Mailing List <linux-nfs@...r.kernel.org>,
linux-fsdevel@...r.kernel.org, LKML <linux-kernel@...r.kernel.org>
Subject: [PATCH 07/10] VFS: hold DCACHE_PAR_UPDATE lock across d_revalidate()
->d_revalidate() functions do not, as a rule, block renames (though 9p
does). Several access the parent of the dentry often, but not always,
taking a reference to be sure it doesn't disappear.
It is in general possible for d_revalidate to race with rename so the
directory the d_revalidate works with may not be the parent of the
dentry by the time the call finishes.
The exact consequence of this will vary between filesystem and may not
be problematic. However it seems safest to provide exclusion between
d_revalidate and other operations that change the dentry such as rename,
when doing so is inexpensive
The introduction of DCACHE_PAR_UPDATE does make this easy. d_revalidate
can set this flag, or wait if it is already set. This ensures there
will be no race, without needing to lock the whole directory.
Signed-off-by: NeilBrown <neilb@...e.de>
---
fs/namei.c | 33 ++++++++++++++++++++++++++++++---
1 file changed, 30 insertions(+), 3 deletions(-)
diff --git a/fs/namei.c b/fs/namei.c
index a7c458cc787c..ef994239fa7c 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -852,10 +852,37 @@ static bool try_to_unlazy_next(struct nameidata *nd, struct dentry *dentry)
static inline int d_revalidate(struct dentry *dentry, unsigned int flags)
{
- if (unlikely(dentry->d_flags & DCACHE_OP_REVALIDATE))
- return dentry->d_op->d_revalidate(dentry, flags);
- else
+ int status;
+
+ if (!unlikely(dentry->d_flags & DCACHE_OP_REVALIDATE))
return 1;
+
+ lock_acquire_exclusive(&dentry->d_update_map, I_MUTEX_NORMAL,
+ 0, NULL, _THIS_IP_);
+ spin_lock(&dentry->d_lock);
+ if (dentry->d_flags & DCACHE_PAR_UPDATE) {
+ /* Some other thread owns this dentry */
+ if (flags & LOOKUP_RCU) {
+ spin_unlock(&dentry->d_lock);
+ lock_map_release(&dentry->d_update_map);
+ return -ECHILD;
+ }
+ ___wait_var_event(&dentry->d_flags,
+ !(dentry->d_flags & DCACHE_PAR_UPDATE),
+ TASK_UNINTERRUPTIBLE, 0, 0,
+ (spin_unlock(&dentry->d_lock),
+ schedule(),
+ spin_lock(&dentry->d_lock))
+ );
+ }
+ dentry->d_flags |= DCACHE_PAR_UPDATE;
+ spin_unlock(&dentry->d_lock);
+
+ status = dentry->d_op->d_revalidate(dentry, flags);
+
+ d_unlock_update(dentry);
+
+ return status;
}
/**
Powered by blists - more mailing lists