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.l...
- 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;
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.l...
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
*/ union { struct llist_node llist;
- @rcu: eventfs_inode to delete in RCU
struct rcu_head rcu; }; unsigned int is_freed:1;struct list_head del_list;
-- 2.42.0
linux-stable-mirror@lists.linaro.org