[v6,8/8] eventfs: Use simple_recursive_removal() to clean up dentries
Commit Message
From: "Steven Rostedt (Google)" <rostedt@goodmis.org>
Looking at how dentry is removed via the tracefs system, I found that
eventfs does not do everything that it did under tracefs. The tracefs
removal of a dentry calls simple_recursive_removal() that does a lot more
than a simple d_invalidate().
As it should be a requirement that any eventfs_inode that has a dentry, so
does its parent. When removing a eventfs_inode, if it has a dentry, a call
to simple_recursive_removal() on that dentry should clean up all the
dentries underneath it.
Add WARN_ON_ONCE() to check for the parent having a dentry if any children
do.
Link: https://lore.kernel.org/all/20231101022553.GE1957730@ZenIV/
Cc: stable@vger.kernel.org
Cc: Al Viro <viro@zeniv.linux.org.uk>
Fixes: 5bdcd5f5331a2 ("eventfs: Implement removal of meta data from eventfs")
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
---
Changes since the last patch: https://lore.kernel.org/linux-trace-kernel/20231031144703.71eef3a0@gandalf.local.home
- Was originally called: eventfs: Process deletion of dentry more thoroughly
- Al Viro pointed out that I could use simple_recursive_removal() instead.
I had originally thought that I could not, but looking deeper into it,
and realizing that if a dentry exists on any eventfs_inode, then all
the parent eventfs_inode of that would als have a dentry. Hence, calling
simple_recursive_removal() on the top dentry would clean up all the
children dentries as well. Doing it his way cleans up the code quite
a bit!
fs/tracefs/event_inode.c | 77 +++++++++++++++++++++++-----------------
fs/tracefs/internal.h | 2 --
2 files changed, 44 insertions(+), 35 deletions(-)
Comments
On Wed, 01 Nov 2023 13:25:49 -0400
Steven Rostedt <rostedt@goodmis.org> wrote:
> From: "Steven Rostedt (Google)" <rostedt@goodmis.org>
>
> Looking at how dentry is removed via the tracefs system, I found that
> eventfs does not do everything that it did under tracefs. The tracefs
> removal of a dentry calls simple_recursive_removal() that does a lot more
> than a simple d_invalidate().
>
> As it should be a requirement that any eventfs_inode that has a dentry, so
> does its parent. When removing a eventfs_inode, if it has a dentry, a call
> to simple_recursive_removal() on that dentry should clean up all the
> dentries underneath it.
>
> Add WARN_ON_ONCE() to check for the parent having a dentry if any children
> do.
This looks good to me.
Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Thanks,
>
> Link: https://lore.kernel.org/all/20231101022553.GE1957730@ZenIV/
>
> Cc: stable@vger.kernel.org
> Cc: Al Viro <viro@zeniv.linux.org.uk>
> Fixes: 5bdcd5f5331a2 ("eventfs: Implement removal of meta data from eventfs")
> Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
> ---
> Changes since the last patch: https://lore.kernel.org/linux-trace-kernel/20231031144703.71eef3a0@gandalf.local.home
>
> - Was originally called: eventfs: Process deletion of dentry more thoroughly
>
> - Al Viro pointed out that I could use simple_recursive_removal() instead.
> I had originally thought that I could not, but looking deeper into it,
> and realizing that if a dentry exists on any eventfs_inode, then all
> the parent eventfs_inode of that would als have a dentry. Hence, calling
> simple_recursive_removal() on the top dentry would clean up all the
> children dentries as well. Doing it his way cleans up the code quite
> a bit!
>
> fs/tracefs/event_inode.c | 77 +++++++++++++++++++++++-----------------
> fs/tracefs/internal.h | 2 --
> 2 files changed, 44 insertions(+), 35 deletions(-)
>
> diff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c
> index 0087a3f455f1..f8a594a50ae6 100644
> --- a/fs/tracefs/event_inode.c
> +++ b/fs/tracefs/event_inode.c
> @@ -967,30 +967,29 @@ static void unhook_dentry(struct dentry *dentry)
> {
> if (!dentry)
> return;
> -
> - /* Keep the dentry from being freed yet (see eventfs_workfn()) */
> + /*
> + * Need to add a reference to the dentry that is expected by
> + * simple_recursive_removal(), which will include a dput().
> + */
> dget(dentry);
>
> - dentry->d_fsdata = NULL;
> - d_invalidate(dentry);
> - mutex_lock(&eventfs_mutex);
> - /* dentry should now have at least a single reference */
> - WARN_ONCE((int)d_count(dentry) < 1,
> - "dentry %px (%s) less than one reference (%d) after invalidate\n",
> - dentry, dentry->d_name.name, d_count(dentry));
> - mutex_unlock(&eventfs_mutex);
> + /*
> + * Also add a reference for the dput() in eventfs_workfn().
> + * That is required as that dput() will free the ei after
> + * the SRCU grace period is over.
> + */
> + dget(dentry);
> }
>
> /**
> * eventfs_remove_rec - remove eventfs dir or file from list
> * @ei: eventfs_inode to be removed.
> - * @head: the list head to place the deleted @ei and children
> * @level: prevent recursion from going more than 3 levels deep.
> *
> * This function recursively removes eventfs_inodes which
> * contains info of files and/or directories.
> */
> -static void eventfs_remove_rec(struct eventfs_inode *ei, struct list_head *head, int level)
> +static void eventfs_remove_rec(struct eventfs_inode *ei, int level)
> {
> struct eventfs_inode *ei_child;
>
> @@ -1009,13 +1008,26 @@ static void eventfs_remove_rec(struct eventfs_inode *ei, struct list_head *head,
> /* search for nested folders or files */
> list_for_each_entry_srcu(ei_child, &ei->children, list,
> lockdep_is_held(&eventfs_mutex)) {
> - eventfs_remove_rec(ei_child, head, level + 1);
> + /* Children only have dentry if parent does */
> + WARN_ON_ONCE(ei_child->dentry && !ei->dentry);
> + eventfs_remove_rec(ei_child, level + 1);
> }
>
> +
> ei->is_freed = 1;
>
> + for (int i = 0; i < ei->nr_entries; i++) {
> + if (ei->d_children[i]) {
> + /* Children only have dentry if parent does */
> + WARN_ON_ONCE(!ei->dentry);
> + unhook_dentry(ei->d_children[i]);
> + }
> + }
> +
> + unhook_dentry(ei->dentry);
> +
> list_del_rcu(&ei->list);
> - list_add_tail(&ei->del_list, head);
> + call_srcu(&eventfs_srcu, &ei->rcu, free_rcu_ei);
> }
>
> /**
> @@ -1026,30 +1038,22 @@ static void eventfs_remove_rec(struct eventfs_inode *ei, struct list_head *head,
> */
> void eventfs_remove_dir(struct eventfs_inode *ei)
> {
> - struct eventfs_inode *tmp;
> - LIST_HEAD(ei_del_list);
> + struct dentry *dentry;
>
> if (!ei)
> return;
>
> - /*
> - * Move the deleted eventfs_inodes onto the ei_del_list
> - * which will also set the is_freed value. Note, this has to be
> - * done under the eventfs_mutex, but the deletions of
> - * the dentries must be done outside the eventfs_mutex.
> - * Hence moving them to this temporary list.
> - */
> mutex_lock(&eventfs_mutex);
> - eventfs_remove_rec(ei, &ei_del_list, 0);
> + dentry = ei->dentry;
> + eventfs_remove_rec(ei, 0);
> mutex_unlock(&eventfs_mutex);
>
> - list_for_each_entry_safe(ei, tmp, &ei_del_list, del_list) {
> - for (int i = 0; i < ei->nr_entries; i++)
> - unhook_dentry(ei->d_children[i]);
> - unhook_dentry(ei->dentry);
> - list_del(&ei->del_list);
> - call_srcu(&eventfs_srcu, &ei->rcu, free_rcu_ei);
> - }
> + /*
> + * If any of the ei children has a dentry, then the ei itself
> + * must have a dentry.
> + */
> + if (dentry)
> + simple_recursive_removal(dentry, NULL);
> }
>
> /**
> @@ -1060,10 +1064,17 @@ void eventfs_remove_dir(struct eventfs_inode *ei)
> */
> void eventfs_remove_events_dir(struct eventfs_inode *ei)
> {
> - struct dentry *dentry = ei->dentry;
> + struct dentry *dentry;
>
> + dentry = ei->dentry;
> eventfs_remove_dir(ei);
>
> - /* Matches the dget() from eventfs_create_events_dir() */
> + /*
> + * Matches the dget() done by tracefs_start_creating()
> + * in eventfs_create_events_dir() when it the dentry was
> + * created. In other words, it's a normal dentry that
> + * sticks around while the other ei->dentry are created
> + * and destroyed dynamically.
> + */
> dput(dentry);
> }
> diff --git a/fs/tracefs/internal.h b/fs/tracefs/internal.h
> index 06a1f220b901..ccee18ca66c7 100644
> --- a/fs/tracefs/internal.h
> +++ b/fs/tracefs/internal.h
> @@ -55,12 +55,10 @@ struct eventfs_inode {
> /*
> * Union - used for deletion
> * @llist: for calling dput() if needed after RCU
> - * @del_list: list of eventfs_inode to delete
> * @rcu: eventfs_inode to delete in RCU
> */
> union {
> struct llist_node llist;
> - struct list_head del_list;
> struct rcu_head rcu;
> };
> unsigned int is_freed:1;
> --
> 2.42.0
@@ -967,30 +967,29 @@ static void unhook_dentry(struct dentry *dentry)
{
if (!dentry)
return;
-
- /* Keep the dentry from being freed yet (see eventfs_workfn()) */
+ /*
+ * Need to add a reference to the dentry that is expected by
+ * simple_recursive_removal(), which will include a dput().
+ */
dget(dentry);
- dentry->d_fsdata = NULL;
- d_invalidate(dentry);
- mutex_lock(&eventfs_mutex);
- /* dentry should now have at least a single reference */
- WARN_ONCE((int)d_count(dentry) < 1,
- "dentry %px (%s) less than one reference (%d) after invalidate\n",
- dentry, dentry->d_name.name, d_count(dentry));
- mutex_unlock(&eventfs_mutex);
+ /*
+ * Also add a reference for the dput() in eventfs_workfn().
+ * That is required as that dput() will free the ei after
+ * the SRCU grace period is over.
+ */
+ dget(dentry);
}
/**
* eventfs_remove_rec - remove eventfs dir or file from list
* @ei: eventfs_inode to be removed.
- * @head: the list head to place the deleted @ei and children
* @level: prevent recursion from going more than 3 levels deep.
*
* This function recursively removes eventfs_inodes which
* contains info of files and/or directories.
*/
-static void eventfs_remove_rec(struct eventfs_inode *ei, struct list_head *head, int level)
+static void eventfs_remove_rec(struct eventfs_inode *ei, int level)
{
struct eventfs_inode *ei_child;
@@ -1009,13 +1008,26 @@ static void eventfs_remove_rec(struct eventfs_inode *ei, struct list_head *head,
/* search for nested folders or files */
list_for_each_entry_srcu(ei_child, &ei->children, list,
lockdep_is_held(&eventfs_mutex)) {
- eventfs_remove_rec(ei_child, head, level + 1);
+ /* Children only have dentry if parent does */
+ WARN_ON_ONCE(ei_child->dentry && !ei->dentry);
+ eventfs_remove_rec(ei_child, level + 1);
}
+
ei->is_freed = 1;
+ for (int i = 0; i < ei->nr_entries; i++) {
+ if (ei->d_children[i]) {
+ /* Children only have dentry if parent does */
+ WARN_ON_ONCE(!ei->dentry);
+ unhook_dentry(ei->d_children[i]);
+ }
+ }
+
+ unhook_dentry(ei->dentry);
+
list_del_rcu(&ei->list);
- list_add_tail(&ei->del_list, head);
+ call_srcu(&eventfs_srcu, &ei->rcu, free_rcu_ei);
}
/**
@@ -1026,30 +1038,22 @@ static void eventfs_remove_rec(struct eventfs_inode *ei, struct list_head *head,
*/
void eventfs_remove_dir(struct eventfs_inode *ei)
{
- struct eventfs_inode *tmp;
- LIST_HEAD(ei_del_list);
+ struct dentry *dentry;
if (!ei)
return;
- /*
- * Move the deleted eventfs_inodes onto the ei_del_list
- * which will also set the is_freed value. Note, this has to be
- * done under the eventfs_mutex, but the deletions of
- * the dentries must be done outside the eventfs_mutex.
- * Hence moving them to this temporary list.
- */
mutex_lock(&eventfs_mutex);
- eventfs_remove_rec(ei, &ei_del_list, 0);
+ dentry = ei->dentry;
+ eventfs_remove_rec(ei, 0);
mutex_unlock(&eventfs_mutex);
- list_for_each_entry_safe(ei, tmp, &ei_del_list, del_list) {
- for (int i = 0; i < ei->nr_entries; i++)
- unhook_dentry(ei->d_children[i]);
- unhook_dentry(ei->dentry);
- list_del(&ei->del_list);
- call_srcu(&eventfs_srcu, &ei->rcu, free_rcu_ei);
- }
+ /*
+ * If any of the ei children has a dentry, then the ei itself
+ * must have a dentry.
+ */
+ if (dentry)
+ simple_recursive_removal(dentry, NULL);
}
/**
@@ -1060,10 +1064,17 @@ void eventfs_remove_dir(struct eventfs_inode *ei)
*/
void eventfs_remove_events_dir(struct eventfs_inode *ei)
{
- struct dentry *dentry = ei->dentry;
+ struct dentry *dentry;
+ dentry = ei->dentry;
eventfs_remove_dir(ei);
- /* Matches the dget() from eventfs_create_events_dir() */
+ /*
+ * Matches the dget() done by tracefs_start_creating()
+ * in eventfs_create_events_dir() when it the dentry was
+ * created. In other words, it's a normal dentry that
+ * sticks around while the other ei->dentry are created
+ * and destroyed dynamically.
+ */
dput(dentry);
}
@@ -55,12 +55,10 @@ struct eventfs_inode {
/*
* Union - used for deletion
* @llist: for calling dput() if needed after RCU
- * @del_list: list of eventfs_inode to delete
* @rcu: eventfs_inode to delete in RCU
*/
union {
struct llist_node llist;
- struct list_head del_list;
struct rcu_head rcu;
};
unsigned int is_freed:1;