[PATCH 6/6] autofs: manage dentry info mount trigger flags better

From: Ian Kent
Date: Fri Jun 17 2022 - 01:36:13 EST


The autofs managed dentry flags are left set for dentries in an
autofs mount directory regardless of whether the dentry should
trigger a mount (a non-empty directory or a symlink doesn't).

But properly managing these flags can sometimes provide the loop
termination condition needed when following mounts which now uses
an -EISDIR return.

Signed-off-by: Ian Kent <raven@xxxxxxxxxx>
---
fs/autofs/root.c | 97 +++++++++++++++++++++++-------------------------------
1 file changed, 42 insertions(+), 55 deletions(-)

diff --git a/fs/autofs/root.c b/fs/autofs/root.c
index ca03c1cae2be..d140d06c5bc6 100644
--- a/fs/autofs/root.c
+++ b/fs/autofs/root.c
@@ -529,9 +529,8 @@ static struct dentry *autofs_lookup(struct inode *dir,

spin_lock(&sbi->lookup_lock);
spin_lock(&dentry->d_lock);
- /* Mark entries in the root as mount triggers */
- if (IS_ROOT(dentry->d_parent) &&
- autofs_type_indirect(sbi->type))
+ /* Mark indirect mount entries as mount triggers */
+ if (autofs_type_indirect(sbi->type))
__managed_dentry_set_managed(dentry);
dentry->d_fsdata = ino;
ino->dentry = dentry;
@@ -567,7 +566,9 @@ static int autofs_dir_symlink(struct user_namespace *mnt_userns,
struct inode *dir, struct dentry *dentry,
const char *symname)
{
+ struct autofs_sb_info *sbi = autofs_sbi(dir->i_sb);
struct autofs_info *ino = autofs_dentry_ino(dentry);
+ struct dentry *parent = dentry->d_parent;
struct autofs_info *p_ino;
struct inode *inode;
size_t size = strlen(symname);
@@ -602,6 +603,16 @@ static int autofs_dir_symlink(struct user_namespace *mnt_userns,

dir->i_mtime = current_time(dir);

+ /* Symlinks don't trigger mounts */
+ managed_dentry_clear_managed(dentry);
+ /* Clear containing directory flags if it's no longer empty */
+ if (autofs_dentry_ino(parent)->count == 2) {
+ /* Don't set or clear type indirect root */
+ if (!IS_ROOT(parent) ||
+ !autofs_type_indirect(sbi->type))
+ managed_dentry_clear_managed(parent);
+ }
+
return 0;
}

@@ -624,12 +635,21 @@ static int autofs_dir_unlink(struct inode *dir, struct dentry *dentry)
{
struct autofs_sb_info *sbi = autofs_sbi(dir->i_sb);
struct autofs_info *ino = autofs_dentry_ino(dentry);
+ struct dentry *parent = dentry->d_parent;
struct autofs_info *p_ino;

p_ino = autofs_dentry_ino(dentry->d_parent);
p_ino->count--;
dput(ino->dentry);

+ /* Set containing directory flags if it's now empty */
+ if (autofs_dentry_ino(parent)->count == 1) {
+ /* Don't set or clear type indirect root */
+ if (!IS_ROOT(parent) ||
+ !autofs_type_indirect(sbi->type))
+ managed_dentry_set_managed(parent);
+ }
+
d_inode(dentry)->i_size = 0;
clear_nlink(d_inode(dentry));

@@ -643,56 +663,11 @@ static int autofs_dir_unlink(struct inode *dir, struct dentry *dentry)
return 0;
}

-/*
- * Version 4 of autofs provides a pseudo direct mount implementation
- * that relies on directories at the leaves of a directory tree under
- * an indirect mount to trigger mounts. To allow for this we need to
- * set the DMANAGED_AUTOMOUNT and DMANAGED_TRANSIT flags on the leaves
- * of the directory tree. There is no need to clear the automount flag
- * following a mount or restore it after an expire because these mounts
- * are always covered. However, it is necessary to ensure that these
- * flags are clear on non-empty directories to avoid unnecessary calls
- * during path walks.
- */
-static void autofs_set_leaf_automount_flags(struct dentry *dentry)
-{
- struct dentry *parent;
-
- /* root and dentrys in the root are already handled */
- if (IS_ROOT(dentry->d_parent))
- return;
-
- managed_dentry_set_managed(dentry);
-
- parent = dentry->d_parent;
- /* only consider parents below dentrys in the root */
- if (IS_ROOT(parent->d_parent))
- return;
- managed_dentry_clear_managed(parent);
-}
-
-static void autofs_clear_leaf_automount_flags(struct dentry *dentry)
-{
- struct dentry *parent;
-
- /* flags for dentrys in the root are handled elsewhere */
- if (IS_ROOT(dentry->d_parent))
- return;
-
- managed_dentry_clear_managed(dentry);
-
- parent = dentry->d_parent;
- /* only consider parents below dentrys in the root */
- if (IS_ROOT(parent->d_parent))
- return;
- if (autofs_dentry_ino(parent)->count == 2)
- managed_dentry_set_managed(parent);
-}
-
static int autofs_dir_rmdir(struct inode *dir, struct dentry *dentry)
{
struct autofs_sb_info *sbi = autofs_sbi(dir->i_sb);
struct autofs_info *ino = autofs_dentry_ino(dentry);
+ struct dentry *parent = dentry->d_parent;
struct autofs_info *p_ino;

pr_debug("dentry %p, removing %pd\n", dentry, dentry);
@@ -705,15 +680,20 @@ static int autofs_dir_rmdir(struct inode *dir, struct dentry *dentry)
d_drop(dentry);
spin_unlock(&sbi->lookup_lock);

- if (sbi->version < 5)
- autofs_clear_leaf_automount_flags(dentry);
-
p_ino = autofs_dentry_ino(dentry->d_parent);
p_ino->count--;
dput(ino->dentry);
d_inode(dentry)->i_size = 0;
clear_nlink(d_inode(dentry));

+ /* Set containing directory flags if it's now empty */
+ if (autofs_dentry_ino(parent)->count == 1) {
+ /* Don't set or clear type indirect root */
+ if (!IS_ROOT(parent) ||
+ !autofs_type_indirect(sbi->type))
+ managed_dentry_set_managed(parent);
+ }
+
if (dir->i_nlink)
drop_nlink(dir);

@@ -726,6 +706,7 @@ static int autofs_dir_mkdir(struct user_namespace *mnt_userns,
{
struct autofs_sb_info *sbi = autofs_sbi(dir->i_sb);
struct autofs_info *ino = autofs_dentry_ino(dentry);
+ struct dentry *parent = dentry->d_parent;
struct autofs_info *p_ino;
struct inode *inode;

@@ -742,15 +723,21 @@ static int autofs_dir_mkdir(struct user_namespace *mnt_userns,
return -ENOMEM;
d_add(dentry, inode);

- if (sbi->version < 5)
- autofs_set_leaf_automount_flags(dentry);
-
dget(dentry);
p_ino = autofs_dentry_ino(dentry->d_parent);
p_ino->count++;
inc_nlink(dir);
dir->i_mtime = current_time(dir);

+ managed_dentry_set_managed(dentry);
+ /* Clear containing directory flags if it's no longer empty */
+ if (autofs_dentry_ino(parent)->count == 2) {
+ /* Don't set or clear type indirect root */
+ if (!IS_ROOT(parent) ||
+ !autofs_type_indirect(sbi->type))
+ managed_dentry_clear_managed(parent);
+ }
+
return 0;
}