eventfs: More updates to eventfs_iterate()
Steven Rostedt
maintainercover eventfs: More updates to eventfs_iterate()
With the ongoing descussion of eventfs iterator, a few more changes are required and some changes are just enhancements.
- Stop immediately in the loop if the ei is found to be in the process of being freed.
- Make the ctx->pos update consistent with the skipped previous read index. This fixes a bug with duplicate files being showned by 'ls'.
- Swap reading ei->entries with ei->children to make the next change easier to read
- Add a "shortcut" in the ei->entries array to skip over already read entries.
Steven Rostedt (Google) (4):
eventfs: Have eventfs_iterate() stop immediately if ei->is_freed is set
eventfs: Do ctx->pos update for all iterations in eventfs_iterate()
eventfs: Read ei->entries before ei->children in eventfs_iterate()
eventfs: Shortcut eventfs_iterate() by skipping entries already read----
fs/tracefs/event_inode.c | 67 ++++++++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 31 deletions(-)
Steven Rostedt
maintainerPATCH 1/4 eventfs: Have eventfs_iterate() stop immediately if ei->is_freed is set
From: "Steven Rostedt (Google)" <[email protected]>
If ei->is_freed is set in eventfs_iterate(), it means that the directory that is being iterated on is in the process of being freed. Just exit the loop immediately when that is ever detected, and separate out the return of the entry->callback() from ei->is_freed.
--- fs/tracefs/event_inode.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-)
fs/tracefs/event_inode.c +6 -5
@@ -788,11 +788,12 @@ static int eventfs_iterate(struct file *file, struct dir_context *ctx) name = entry->name;mutex_lock (&eventfs_mutex); -/* If ei->is_freed, then the event itself may be too */ -if (!ei->is_freed) - r = entry->callback (name, &mode, &cdata, &fops); -else - r =-1 ; +/* If ei->is_freed then just bail here, nothing more to do */ +if (ei->is_freed) { +mutex_unlock (&eventfs_mutex); + goto out; + } + r = entry->callback (name, &mode, &cdata, &fops);mutex_unlock (&eventfs_mutex);if (r <=0 )continue ;
Signature · 1 line
2.42.0
Steven Rostedt
maintainerPATCH 2/4 eventfs: Do ctx->pos update for all iterations in eventfs_iterate()
In reply to Steven RostedtFrom: "Steven Rostedt (Google)" <[email protected]>
The ctx->pos was only updated when it added an entry, but the "skip to current pos" check (c--) happened for every loop regardless of if the entry was added or not. This inconsistency caused readdir to be incorrect.
It was due to:
for (i = 0; i < ei->nr_entries; i++) {
if (c > 0) {
c--;
continue;
}
mutex_lock(&eventfs_mutex);
/* If ei->is_freed then just bail here, nothing more to do */
if (ei->is_freed) {
mutex_unlock(&eventfs_mutex);
goto out;
}
r = entry->callback(name, &mode, &cdata, &fops);
mutex_unlock(&eventfs_mutex);
[..]
ctx->pos++;
}But this can cause the iterator to return a file that was already read. That's because of the way the callback() works. Some events may not have all files, and the callback can return 0 to tell eventfs to skip the file for this directory.
for instance, we have:
# ls /sys/kernel/tracing/events/ftrace/function format hist hist_debug id inject
and
# ls /sys/kernel/tracing/events/sched/sched_switch/ enable filter format hist hist_debug id inject trigger
Where the function directory is missing "enable", "filter" and "trigger". That's because the callback() for events has:
static int event_callback(const char *name, umode_t *mode, void **data,
const struct file_operations **fops)
{
struct trace_event_file *file = *data;
struct trace_event_call *call = file->event_call;[..]
/*
* Only event directories that can be enabled should have
* triggers or filters, with the exception of the "print"
* event that can have a "trigger" file.
*/
if (!(call->flags & TRACE_EVENT_FL_IGNORE_ENABLE)) {
if (call->class->reg && strcmp(name, "enable") == 0) {
*mode = TRACE_MODE_WRITE;
*fops = &ftrace_enable_fops;
return 1;
}
if (strcmp(name, "filter") == 0) {
*mode = TRACE_MODE_WRITE;
*fops = &ftrace_event_filter_fops;
return 1;
}
}
if (!(call->flags & TRACE_EVENT_FL_IGNORE_ENABLE) ||
strcmp(trace_event_name(call), "print") == 0) {
if (strcmp(name, "trigger") == 0) {
*mode = TRACE_MODE_WRITE;
*fops = &event_trigger_fops;
return 1;
}
}
[..]
return 0;
}Where the function event has the TRACE_EVENT_FL_IGNORE_ENABLE set.
This means that the entries array elements for "enable", "filter" and "trigger" when called on the function event will have the callback return 0 and not 1, to tell eventfs to skip these files for it.
Because the "skip to current ctx->pos" check happened for all entries, but the ctx->pos++ only happened to entries that exist, it would confuse the reading of a directory. Which would cause:
# ls /sys/kernel/tracing/events/ftrace/function/ format hist hist hist_debug hist_debug id inject inject
The missing "enable", "filter" and "trigger" caused ls to show "hist", "hist_debug" and "inject" twice.
Update the ctx->pos for every iteration to keep its update and the "skip" update consistent. This also means that on error, the ctx->pos needs to be decremented if it was incremented without adding something.
--- fs/tracefs/event_inode.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-)
fs/tracefs/event_inode.c +14 -7
@@ -760,6 +760,8 @@ static int eventfs_iterate(struct file *file, struct dir_context *ctx)continue ; } + ctx->pos++; +if (ei_child->is_freed)continue ;
@@ -767,13 +769,12 @@ static int eventfs_iterate(struct file *file, struct dir_context *ctx) dentry =create_dir_dentry (ei, ei_child, ei_dentry);if (!dentry) - goto out; + goto out_dec; ino = dentry->d_inode->i_ino;dput (dentry);if (!dir_emit (ctx, name,strlen (name), ino,DT_DIR )) - goto out; - ctx->pos++; + goto out_dec; }for (i =0 ; i < ei->nr_entries; i++) {
@@ -784,6 +785,8 @@ static int eventfs_iterate(struct file *file, struct dir_context *ctx)continue ; } + ctx->pos++; + entry = &ei->entries[i]; name = entry->name;
@@ -791,7 +794,7 @@ static int eventfs_iterate(struct file *file, struct dir_context *ctx)/* If ei->is_freed then just bail here, nothing more to do */ if (ei->is_freed) {mutex_unlock (&eventfs_mutex); - goto out; + goto out_dec; } r = entry->callback (name, &mode, &cdata, &fops);mutex_unlock (&eventfs_mutex);
@@ -800,19 +803,23 @@ static int eventfs_iterate(struct file *file, struct dir_context *ctx) dentry =create_file_dentry (ei, i, ei_dentry, name, mode, cdata, fops);if (!dentry) - goto out; + goto out_dec; ino = dentry->d_inode->i_ino;dput (dentry);if (!dir_emit (ctx, name,strlen (name), ino,DT_REG )) - goto out; - ctx->pos++; + goto out_dec; } ret =1 ; out:srcu_read_unlock (&eventfs_srcu, idx);return ret; + + out_dec: +/* Incremented ctx->pos without adding something, reset it */ + ctx->pos--; + goto out; }/**
Signature · 1 line
2.42.0
Steven Rostedt
maintainerPATCH 3/4 eventfs: Read ei->entries before ei->children in eventfs_iterate()
In reply to Steven RostedtFrom: "Steven Rostedt (Google)" <[email protected]>
In order to apply a shortcut to skip over the current ctx->pos immediately, by using the ei->entries array, the reading of that array should be first. Moving the array reading before the linked list reading will make the shortcut change diff nicer to read.
--- fs/tracefs/event_inode.c | 46 ++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 23 deletions(-)
fs/tracefs/event_inode.c +23 -23
@@ -752,8 +752,8 @@ static int eventfs_iterate(struct file *file, struct dir_context *ctx)* Need to create the dentries and inodes to have a consistent * inode number. */ -list_for_each_entry_srcu (ei_child, &ei->children, list, -srcu_read_lock_held (&eventfs_srcu)) { +for (i =0 ; i < ei->nr_entries; i++) { +void *cdata = ei->data;if (c >0 ) { c--;
@@ -762,23 +762,32 @@ static int eventfs_iterate(struct file *file, struct dir_context *ctx) ctx->pos++; -if (ei_child->is_freed) -continue ; + entry = &ei->entries[i]; + name = entry->name; - name = ei_child->name; +mutex_lock (&eventfs_mutex); +/* If ei->is_freed then just bail here, nothing more to do */ +if (ei->is_freed) { +mutex_unlock (&eventfs_mutex); + goto out_dec; + } + r = entry->callback (name, &mode, &cdata, &fops); +mutex_unlock (&eventfs_mutex); +if (r <=0 ) +continue ; - dentry =create_dir_dentry (ei, ei_child, ei_dentry); + dentry =create_file_dentry (ei, i, ei_dentry, name, mode, cdata, fops);if (!dentry) goto out_dec; ino = dentry->d_inode->i_ino;dput (dentry); -if (!dir_emit (ctx, name,strlen (name), ino,DT_DIR )) +if (!dir_emit (ctx, name,strlen (name), ino,DT_REG )) goto out_dec; } -for (i =0 ; i < ei->nr_entries; i++) { -void *cdata = ei->data; +list_for_each_entry_srcu (ei_child, &ei->children, list, +srcu_read_lock_held (&eventfs_srcu)) {if (c >0 ) { c--;
@@ -787,27 +796,18 @@ static int eventfs_iterate(struct file *file, struct dir_context *ctx) ctx->pos++; - entry = &ei->entries[i]; - name = entry->name; - -mutex_lock (&eventfs_mutex); -/* If ei->is_freed then just bail here, nothing more to do */ -if (ei->is_freed) { -mutex_unlock (&eventfs_mutex); - goto out_dec; - } - r = entry->callback (name, &mode, &cdata, &fops); -mutex_unlock (&eventfs_mutex); -if (r <=0 ) +if (ei_child->is_freed)continue ; - dentry =create_file_dentry (ei, i, ei_dentry, name, mode, cdata, fops); + name = ei_child->name; + + dentry =create_dir_dentry (ei, ei_child, ei_dentry);if (!dentry) goto out_dec; ino = dentry->d_inode->i_ino;dput (dentry); -if (!dir_emit (ctx, name,strlen (name), ino,DT_REG )) +if (!dir_emit (ctx, name,strlen (name), ino,DT_DIR )) goto out_dec; } ret =1 ;
Signature · 1 line
2.42.0
Steven Rostedt
maintainerPATCH 4/4 eventfs: Shortcut eventfs_iterate() by skipping entries already read
In reply to Steven RostedtFrom: "Steven Rostedt (Google)" <[email protected]>
As the ei->entries array is fixed for the duration of the eventfs_inode, it can be used to skip over already read entries in eventfs_iterate().
That is, if ctx->pos is greater than zero, there's no reason in doing the loop across the ei->entries array for the entries less than ctx->pos. Instead, start the lookup of the entries at the current ctx->pos.
--- fs/tracefs/event_inode.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-)
fs/tracefs/event_inode.c +10 -13
@@ -746,21 +746,15 @@ static int eventfs_iterate(struct file *file, struct dir_context *ctx)if (!ei || !ei_dentry) goto out; - ret =0 ; -/* * Need to create the dentries and inodes to have a consistent * inode number. */ -for (i =0 ; i < ei->nr_entries; i++) { -void *cdata = ei->data; - -if (c >0 ) { - c--; -continue ; - } + ret =0 ; - ctx->pos++; +/* Start at 'c' to jump over already read entries */ +for (i = c; i < ei->nr_entries; i++, ctx->pos++) { +void *cdata = ei->data; entry = &ei->entries[i]; name = entry->name;
@@ -769,7 +763,7 @@ static int eventfs_iterate(struct file *file, struct dir_context *ctx)/* If ei->is_freed then just bail here, nothing more to do */ if (ei->is_freed) {mutex_unlock (&eventfs_mutex); - goto out_dec; + goto out; } r = entry->callback (name, &mode, &cdata, &fops);mutex_unlock (&eventfs_mutex);
@@ -778,14 +772,17 @@ static int eventfs_iterate(struct file *file, struct dir_context *ctx) dentry =create_file_dentry (ei, i, ei_dentry, name, mode, cdata, fops);if (!dentry) - goto out_dec; + goto out; ino = dentry->d_inode->i_ino;dput (dentry);if (!dir_emit (ctx, name,strlen (name), ino,DT_REG )) - goto out_dec; + goto out; } +/* Subtract the skipped entries above */ + c -=min ((unsigned int )c, (unsigned int )ei->nr_entries); +list_for_each_entry_srcu (ei_child, &ei->children, list,srcu_read_lock_held (&eventfs_srcu)) {
Signature · 1 line
2.42.0