diff mbox series

dcache: rename d_genocide()

Message ID 20240210100643.2207350-1-amir73il@gmail.com (mailing list archive)
State New
Headers show
Series dcache: rename d_genocide() | expand

Commit Message

Amir Goldstein Feb. 10, 2024, 10:06 a.m. UTC
Political context aside, using analogies from the real world in code
is supposed to help us human programmers understand the code better.

In the case of d_genocide(), not only is it a very dark analogy, but it's
also a bad one, because d_genocide() does not actually kill any dentries.

Rename it to dput_dcache_for_umount() and rename the DCACHE_GENOCIDE
flag to DCACHE_SB_DYING.

Signed-off-by: Amir Goldstein <amir73il@gmail.com>
---

Al,

I am not usually for PC culture and I know that you are on team
"freedom of speech" ;-), but IMO this one stood out for its high ratio
of bad taste vs. usefulness.

The patch is based on your revert of "get rid of DCACHE_GENOCIDE".
I was hoping that you could queue my patch along with the revert.

BTW, why was d_genocide() only dropping refcounts on the s_root tree
and not on the s_roots trees like shrink_dcache_for_umount()?
Is it because dentries on s_roots are not supposed to be hashed?

Thanks,
Amir.

 fs/dcache.c            | 13 ++++++++-----
 fs/internal.h          |  2 +-
 fs/super.c             |  3 +--
 include/linux/dcache.h |  2 +-
 4 files changed, 11 insertions(+), 9 deletions(-)

Comments

Al Viro Feb. 10, 2024, 11:27 p.m. UTC | #1
On Sat, Feb 10, 2024 at 12:06:43PM +0200, Amir Goldstein wrote:

> I am not usually for PC culture and I know that you are on team
> "freedom of speech" ;-), but IMO this one stood out for its high ratio
> of bad taste vs. usefulness.
> 
> The patch is based on your revert of "get rid of DCACHE_GENOCIDE".
> I was hoping that you could queue my patch along with the revert.
> 
> BTW, why was d_genocide() only dropping refcounts on the s_root tree
> and not on the s_roots trees like shrink_dcache_for_umount()?
> Is it because dentries on s_roots are not supposed to be hashed?

Because secondary roots make no sense for "everything's in dcache"
kind of filesystems, mostly.

FWIW, I don't believe that cosmetic renaming is the right thing to
do here.  The real issue here is that those fs-pinned dentries are
hard to distinguish.  The rule is "if dentry on such filesystem is
positive and hashed, that contributes 1 to its refcount".

Unfortunately, that doesn't come with sane locking rules.
If nothing else, I would rather have an explicit flag set along
with bumping ->d_count on creation side and cleared along with
dropping refcount on removal, both under ->d_lock.

Another piece of ugliness is the remaining places that try to
open-code simple_recursive_removal(); they get it wrong more
often than not.  Connected to the previous, since that's where
those games with simple_unlink()/simple_rmdir() and associated
fun with refcounts tend to happen.

I'm trying to untangle that mess - on top of that revert,
obviously.  Interposing your patch in there is doable, of course,
but it's not particularly useful, IMO, especially since the
whole d_genocide() thing is quite likely to disappear, turning
kill_litter_super() into an alias for kill_anon_super().
Amir Goldstein Feb. 11, 2024, 8 a.m. UTC | #2
On Sun, Feb 11, 2024 at 1:27 AM Al Viro <viro@zeniv.linux.org.uk> wrote:
>
> On Sat, Feb 10, 2024 at 12:06:43PM +0200, Amir Goldstein wrote:
>
> > I am not usually for PC culture and I know that you are on team
> > "freedom of speech" ;-), but IMO this one stood out for its high ratio
> > of bad taste vs. usefulness.
> >
> > The patch is based on your revert of "get rid of DCACHE_GENOCIDE".
> > I was hoping that you could queue my patch along with the revert.
> >
> > BTW, why was d_genocide() only dropping refcounts on the s_root tree
> > and not on the s_roots trees like shrink_dcache_for_umount()?
> > Is it because dentries on s_roots are not supposed to be hashed?
>
> Because secondary roots make no sense for "everything's in dcache"
> kind of filesystems, mostly.
>
> FWIW, I don't believe that cosmetic renaming is the right thing to
> do here.  The real issue here is that those fs-pinned dentries are
> hard to distinguish.  The rule is "if dentry on such filesystem is
> positive and hashed, that contributes 1 to its refcount".
>
> Unfortunately, that doesn't come with sane locking rules.
> If nothing else, I would rather have an explicit flag set along
> with bumping ->d_count on creation side and cleared along with
> dropping refcount on removal, both under ->d_lock.
>
> Another piece of ugliness is the remaining places that try to
> open-code simple_recursive_removal(); they get it wrong more
> often than not.  Connected to the previous, since that's where
> those games with simple_unlink()/simple_rmdir() and associated
> fun with refcounts tend to happen.
>
> I'm trying to untangle that mess - on top of that revert, obviously.

That sounds perfect.

> Interposing your patch in there is doable, of course,
> but it's not particularly useful, IMO, especially since the

Merging my rename patch is not the goal.
Clarifying the code is.

> whole d_genocide() thing is quite likely to disappear, turning
> kill_litter_super() into an alias for kill_anon_super().

2-in-1, getting rid of cruelty in the human and animal kingdom ;)

Thanks,
Amir.
Al Viro Feb. 11, 2024, 6:44 p.m. UTC | #3
On Sun, Feb 11, 2024 at 10:00:03AM +0200, Amir Goldstein wrote:

> > whole d_genocide() thing is quite likely to disappear, turning
> > kill_litter_super() into an alias for kill_anon_super().
> 
> 2-in-1, getting rid of cruelty in the human and animal kingdom ;)

FWIW, "litter" in the above is in the sense of "trash", not "collection
of animal offspring from the same birth"...
Amir Goldstein Feb. 12, 2024, 7:02 a.m. UTC | #4
On Sun, Feb 11, 2024 at 8:44 PM Al Viro <viro@zeniv.linux.org.uk> wrote:
>
> On Sun, Feb 11, 2024 at 10:00:03AM +0200, Amir Goldstein wrote:
>
> > > whole d_genocide() thing is quite likely to disappear, turning
> > > kill_litter_super() into an alias for kill_anon_super().
> >
> > 2-in-1, getting rid of cruelty in the human and animal kingdom ;)
>
> FWIW, "litter" in the above is in the sense of "trash", not "collection
> of animal offspring from the same birth"...

Well either way, it is not clear by this name when the function should be used.
The current documentation of when kill_litter_super() should be used is
"use it for fs that set FS_LITTER flag, oh, BTW, there is no FS_LITTER flag":

Documentation/filesystems/porting.rst:
---
new file_system_type method - kill_sb(superblock).  If you are converting
an existing filesystem, set it according to ->fs_flags::

        FS_REQUIRES_DEV         -       kill_block_super
        FS_LITTER               -       kill_litter_super
        neither                 -       kill_anon_super

FS_LITTER is gone - just remove it from fs_flags.
---

If you are going to make kill_litter_super() an alias of kill_anon_super()
I suggest going the extra mile and replacing the remaining 30 instances of
kill_litter_super().

If the rules become straight forward for the default ->kill_sb(),
then maybe we should even make ->kill_sb() optional and do:

diff --git a/fs/super.c b/fs/super.c
index d35e85295489..6200cac0e4f8 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -458,6 +458,18 @@ static void kill_super_notify(struct super_block *sb)
        super_wake(sb, SB_DEAD);
 }

+static void kill_sb(struct super_block *s)
+{
+       struct file_system_type *fs = s->s_type;
+
+       if (fs->kill_sb)
+               fs->kill_sb(s);
+       else if (fs->fs_flags & FS_REQUIRES_DEV)
+               kill_block_super(s);
+       else
+               kill_anon_super(s);
+}
+
 /**
  *     deactivate_locked_super -       drop an active reference to superblock
  *     @s: superblock to deactivate
@@ -474,7 +486,7 @@ void deactivate_locked_super(struct super_block *s)
        struct file_system_type *fs = s->s_type;
        if (atomic_dec_and_test(&s->s_active)) {
                shrinker_free(s->s_shrink);
-               fs->kill_sb(s);
+               kill_sb(s);

                kill_super_notify(s);
 --

Thanks,
Amir.
Al Viro Feb. 12, 2024, 8:09 a.m. UTC | #5
On Mon, Feb 12, 2024 at 09:02:58AM +0200, Amir Goldstein wrote:

> If you are going to make kill_litter_super() an alias of kill_anon_super()
> I suggest going the extra mile and replacing the remaining 30 instances of
> kill_litter_super().
> 
> If the rules become straight forward for the default ->kill_sb(),
> then maybe we should even make ->kill_sb() optional and do:
> 
> diff --git a/fs/super.c b/fs/super.c
> index d35e85295489..6200cac0e4f8 100644
> --- a/fs/super.c
> +++ b/fs/super.c
> @@ -458,6 +458,18 @@ static void kill_super_notify(struct super_block *sb)
>         super_wake(sb, SB_DEAD);
>  }
> 
> +static void kill_sb(struct super_block *s)
> +{
> +       struct file_system_type *fs = s->s_type;
> +
> +       if (fs->kill_sb)
> +               fs->kill_sb(s);
> +       else if (fs->fs_flags & FS_REQUIRES_DEV)
> +               kill_block_super(s);
> +       else
> +               kill_anon_super(s);
> +}

Bloody bad idea, IMO.  Note that straight use of kill_anon_super()
pretty much forces you into doing everything from ->put_super().
And that leads to rather clumsy failure exits in foo_fill_super(),
since you *won't* get ->put_super() called unless you've got to
setting ->s_root.

Considering how easily the failure exits rot, I'd rather discourage
that variant.
Al Viro Feb. 13, 2024, 4:42 a.m. UTC | #6
On Mon, Feb 12, 2024 at 08:09:26AM +0000, Al Viro wrote:

> Bloody bad idea, IMO.  Note that straight use of kill_anon_super()
> pretty much forces you into doing everything from ->put_super().
> And that leads to rather clumsy failure exits in foo_fill_super(),
> since you *won't* get ->put_super() called unless you've got to
> setting ->s_root.
> 
> Considering how easily the failure exits rot, I'd rather discourage
> that variant.

BTW, take a look at fs/ext2/super.c and compare the mess in failure
exits in ext2_fill_super() with ext2_put_super().  See the amount of
duplication?

In case of ext2_fill_super() success eventual ->kill_sb() will call
->put_super() (from generic_shutdown_super(), from kill_block_super()).

What happens in case of ext2_fill_super() failure?  ->kill_sb() is called,
but ->put_super() is only called if ->s_root is non-NULL (and at the very
least it requires ->s_op to have been set).  So in that case we have
ext2_fill_super() manually undo the allocations, etc. it had managed to do,
same as ext2_put_super() would've done.

If that stuff gets lifted into ->kill_sb(), we get the bulk of ext2_put_super()
moved into ext2_kill_super() (I wouldn't be surprised if ext2_put_super()
completely disappeared, actually), with all those goto failed_mount<something>
in ext2_fill_super() turning into plain return -E...
Amir Goldstein Feb. 13, 2024, 6:42 a.m. UTC | #7
On Tue, Feb 13, 2024 at 6:42 AM Al Viro <viro@zeniv.linux.org.uk> wrote:
>
> On Mon, Feb 12, 2024 at 08:09:26AM +0000, Al Viro wrote:
>
> > Bloody bad idea, IMO.  Note that straight use of kill_anon_super()
> > pretty much forces you into doing everything from ->put_super().
> > And that leads to rather clumsy failure exits in foo_fill_super(),
> > since you *won't* get ->put_super() called unless you've got to
> > setting ->s_root.
> >
> > Considering how easily the failure exits rot, I'd rather discourage
> > that variant.
>
> BTW, take a look at fs/ext2/super.c and compare the mess in failure
> exits in ext2_fill_super() with ext2_put_super().  See the amount of
> duplication?
>
> In case of ext2_fill_super() success eventual ->kill_sb() will call
> ->put_super() (from generic_shutdown_super(), from kill_block_super()).
>
> What happens in case of ext2_fill_super() failure?  ->kill_sb() is called,
> but ->put_super() is only called if ->s_root is non-NULL (and at the very
> least it requires ->s_op to have been set).  So in that case we have
> ext2_fill_super() manually undo the allocations, etc. it had managed to do,
> same as ext2_put_super() would've done.
>
> If that stuff gets lifted into ->kill_sb(), we get the bulk of ext2_put_super()
> moved into ext2_kill_super() (I wouldn't be surprised if ext2_put_super()
> completely disappeared, actually), with all those goto failed_mount<something>
> in ext2_fill_super() turning into plain return -E...

That sounds good, but I am not sure what you are advocating for?

What I wrote is that if kill_litter_super() becomes an alias of
kill_anon_super(),
then spraying kill_{anon,block}_super() automatically for new fs does not make
much sense from API POV.

It sounds like you are suggesting that the use of
kill_{anon,block}_super() should
be discouraged and that ext2 could be used to set an example.
I did not understand if you are suggesting API changes to encourage customized
->kill_sb()?

Thanks,
Amir.
diff mbox series

Patch

diff --git a/fs/dcache.c b/fs/dcache.c
index 6ebccba33336..61ecc98c49a8 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -3054,24 +3054,27 @@  bool is_subdir(struct dentry *new_dentry, struct dentry *old_dentry)
 }
 EXPORT_SYMBOL(is_subdir);
 
-static enum d_walk_ret d_genocide_kill(void *data, struct dentry *dentry)
+/* make umount_check() happy before killing sb */
+static enum d_walk_ret dput_for_umount(void *data, struct dentry *dentry)
 {
 	struct dentry *root = data;
 	if (dentry != root) {
 		if (d_unhashed(dentry) || !dentry->d_inode)
 			return D_WALK_SKIP;
 
-		if (!(dentry->d_flags & DCACHE_GENOCIDE)) {
-			dentry->d_flags |= DCACHE_GENOCIDE;
+		if (!(dentry->d_flags & DCACHE_SB_DYING)) {
+			dentry->d_flags |= DCACHE_SB_DYING;
 			dentry->d_lockref.count--;
 		}
 	}
 	return D_WALK_CONTINUE;
 }
 
-void d_genocide(struct dentry *parent)
+/* drop last references before shrink_dcache_for_umount() */
+void dput_dcache_for_umount(struct super_block *sb)
 {
-	d_walk(parent, parent, d_genocide_kill);
+	if (sb->s_root)
+		d_walk(sb->s_root, sb->s_root, dput_for_umount);
 }
 
 void d_mark_tmpfile(struct file *file, struct inode *inode)
diff --git a/fs/internal.h b/fs/internal.h
index b67406435fc0..27bda8a3ff9d 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -215,10 +215,10 @@  extern char *simple_dname(struct dentry *, char *, int);
 extern void dput_to_list(struct dentry *, struct list_head *);
 extern void shrink_dentry_list(struct list_head *);
 extern void shrink_dcache_for_umount(struct super_block *);
+extern void dput_dcache_for_umount(struct super_block *);
 extern struct dentry *__d_lookup(const struct dentry *, const struct qstr *);
 extern struct dentry *__d_lookup_rcu(const struct dentry *parent,
 				const struct qstr *name, unsigned *seq);
-extern void d_genocide(struct dentry *);
 
 /*
  * pipe.c
diff --git a/fs/super.c b/fs/super.c
index d35e85295489..42b3189fbe06 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -1235,8 +1235,7 @@  EXPORT_SYMBOL(kill_anon_super);
 
 void kill_litter_super(struct super_block *sb)
 {
-	if (sb->s_root)
-		d_genocide(sb->s_root);
+	dput_dcache_for_umount(sb);
 	kill_anon_super(sb);
 }
 EXPORT_SYMBOL(kill_litter_super);
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index d07cf2f1bb7d..0ce8543b64d7 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -173,7 +173,7 @@  struct dentry_operations {
 #define DCACHE_DONTCACHE		BIT(7) /* Purge from memory on final dput() */
 
 #define DCACHE_CANT_MOUNT		BIT(8)
-#define DCACHE_GENOCIDE			BIT(9)
+#define DCACHE_SB_DYING			BIT(9)
 #define DCACHE_SHRINK_LIST		BIT(10)
 
 #define DCACHE_OP_WEAK_REVALIDATE	BIT(11)