summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Brauner <brauner@kernel.org>2024-07-02 21:03:26 +0200
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2024-07-27 10:40:21 +0200
commitf65bffb464408cb1e7a6feb542a7234a38fde0d6 (patch)
treeb5843bfcfe0e85127aaa957ab37d869b8bfeeca4
parentddeda6ca5f218b668b560d90fc31ae469adbfd92 (diff)
downloadlinux-f65bffb464408cb1e7a6feb542a7234a38fde0d6.tar.gz
linux-f65bffb464408cb1e7a6feb542a7234a38fde0d6.tar.bz2
linux-f65bffb464408cb1e7a6feb542a7234a38fde0d6.zip
fs: better handle deep ancestor chains in is_subdir()
[ Upstream commit 391b59b045004d5b985d033263ccba3e941a7740 ] Jan reported that 'cd ..' may take a long time in deep directory hierarchies under a bind-mount. If concurrent renames happen it is possible to livelock in is_subdir() because it will keep retrying. Change is_subdir() from simply retrying over and over to retry once and then acquire the rename lock to handle deep ancestor chains better. The list of alternatives to this approach were less then pleasant. Change the scope of rcu lock to cover the whole walk while at it. A big thanks to Jan and Linus. Both Jan and Linus had proposed effectively the same thing just that one version ended up being slightly more elegant. Reported-by: Jan Kara <jack@suse.cz> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Christian Brauner <brauner@kernel.org> Signed-off-by: Sasha Levin <sashal@kernel.org>
-rw-r--r--fs/dcache.c31
1 files changed, 14 insertions, 17 deletions
diff --git a/fs/dcache.c b/fs/dcache.c
index 406a71abb1b5..5febd219fdeb 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -3092,28 +3092,25 @@ EXPORT_SYMBOL(d_splice_alias);
bool is_subdir(struct dentry *new_dentry, struct dentry *old_dentry)
{
- bool result;
+ bool subdir;
unsigned seq;
if (new_dentry == old_dentry)
return true;
- do {
- /* for restarting inner loop in case of seq retry */
- seq = read_seqbegin(&rename_lock);
- /*
- * Need rcu_readlock to protect against the d_parent trashing
- * due to d_move
- */
- rcu_read_lock();
- if (d_ancestor(old_dentry, new_dentry))
- result = true;
- else
- result = false;
- rcu_read_unlock();
- } while (read_seqretry(&rename_lock, seq));
-
- return result;
+ /* Access d_parent under rcu as d_move() may change it. */
+ rcu_read_lock();
+ seq = read_seqbegin(&rename_lock);
+ subdir = d_ancestor(old_dentry, new_dentry);
+ /* Try lockless once... */
+ if (read_seqretry(&rename_lock, seq)) {
+ /* ...else acquire lock for progress even on deep chains. */
+ read_seqlock_excl(&rename_lock);
+ subdir = d_ancestor(old_dentry, new_dentry);
+ read_sequnlock_excl(&rename_lock);
+ }
+ rcu_read_unlock();
+ return subdir;
}
EXPORT_SYMBOL(is_subdir);