@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
tracefs-objs := inode.o
tracefs-objs += event_inode.o
+tracefs-objs += event_show.o
obj-$(CONFIG_TRACING) += tracefs.o
@@ -23,47 +23,7 @@
#include <linux/delay.h>
#include "internal.h"
-struct eventfs_inode {
- struct list_head e_top_files;
-};
-
-/*
- * struct eventfs_file - hold the properties of the eventfs files and
- * directories.
- * @name: the name of the file or directory to create
- * @d_parent: holds parent's dentry
- * @dentry: once accessed holds dentry
- * @list: file or directory to be added to parent directory
- * @ei: list of files and directories within directory
- * @fop: file_operations for file or directory
- * @iop: inode_operations for file or directory
- * @data: something that the caller will want to get to later on
- * @mode: the permission that the file or directory should have
- */
-struct eventfs_file {
- const char *name;
- struct dentry *d_parent;
- struct dentry *dentry;
- struct list_head list;
- struct eventfs_inode *ei;
- const struct file_operations *fop;
- const struct inode_operations *iop;
- /*
- * Union - used for deletion
- * @del_list: list of eventfs_file to delete
- * @rcu: eventfs_file to delete in RCU
- * @is_freed: node is freed if one of the above is set
- */
- union {
- struct list_head del_list;
- struct rcu_head rcu;
- unsigned long is_freed;
- };
- void *data;
- umode_t mode;
-};
-
-static DEFINE_MUTEX(eventfs_mutex);
+DEFINE_MUTEX(eventfs_mutex);
DEFINE_STATIC_SRCU(eventfs_srcu);
static struct dentry *eventfs_root_lookup(struct inode *dir,
new file mode 100644
@@ -0,0 +1,147 @@
+#include <linux/seq_file.h>
+#include <linux/tracefs.h>
+#include "internal.h"
+
+/*
+ * This will iterate three lists that correspond to the directory level
+ * of the eventfs directory.
+ *
+ * level 0 : /sys/kernel/tracing/events
+ * level 1 : /sys/kernel/tracing/events/<system>
+ * level 2 : /sys/kernel/tracing/events/<system>/event
+ *
+ * The iterator needs to see all levels as they all contain dynamically
+ * allocated dentries and inodes.
+ */
+struct event_list {
+ int level;
+ struct list_head *head[3];
+ struct list_head *next[3];
+};
+
+static void *e_next(struct seq_file *m, void *v, loff_t *pos)
+{
+ struct event_list *elist = m->private;
+ int level = elist->level;
+ struct list_head *head = elist->head[level];
+ struct list_head *next = elist->next[level];
+ struct eventfs_file *ef;
+
+ (*pos)++;
+
+ /* If next is equal to head, then the list is complete */
+ while (next == head) {
+ if (!level)
+ return NULL;
+
+ /* sublevel below top level, go up one */
+ elist->level = --level;
+ head = elist->head[level];
+ /* Going down does not update next, so do it here */
+ next = elist->next[level]->next;
+ elist->next[level] = next;
+ }
+
+ ef = list_entry(next, struct eventfs_file, list);
+
+ /* For each entry (not at the bottom) do a breadth first search */
+ if (ef->ei && !list_empty(&ef->ei->e_top_files) && level < 2) {
+ elist->level = ++level;
+ head = &ef->ei->e_top_files;
+ elist->head[level] = head;
+ next = head;
+ /*
+ * Note, next is now pointing to the next sub level.
+ * Need to update the next for the previous level on the way up.
+ */
+ }
+
+ elist->next[level] = next->next;
+ return ef;
+}
+
+static void *e_start(struct seq_file *m, loff_t *pos)
+{
+ struct event_list *elist = m->private;
+ struct eventfs_file *ef = NULL;
+ loff_t l;
+
+ mutex_lock(&eventfs_mutex);
+
+ elist->level = 0;
+ elist->next[0] = elist->head[0]->next;
+
+ for (l = 0; l <= *pos; ) {
+ ef = e_next(m, ef, &l);
+ if (!ef)
+ break;
+ }
+ return ef;
+}
+
+static int e_show(struct seq_file *m, void *v)
+{
+ struct eventfs_file *ef = v;
+
+ seq_printf(m, "%s", ef->name);
+ if (ef->ei)
+ seq_putc(m, '/');
+
+ if (ef->dentry)
+ seq_printf(m, " dentry: (%d)", d_count(ef->dentry));
+ seq_putc(m, '\n');
+
+ return 0;
+}
+
+static void e_stop(struct seq_file *m, void *p)
+{
+ mutex_unlock(&eventfs_mutex);
+}
+
+static const struct seq_operations eventfs_show_dentry_seq_ops = {
+ .start = e_start,
+ .next = e_next,
+ .show = e_show,
+ .stop = e_stop,
+};
+
+static int
+eventfs_show_dentry_open(struct inode *inode, struct file *file)
+{
+ const struct seq_operations *seq_ops = &eventfs_show_dentry_seq_ops;
+ struct event_list *elist;
+ struct tracefs_inode *ti;
+ struct eventfs_inode *ei;
+ struct dentry *dentry;
+
+ /* The inode private should have the dentry of the "events" directory */
+ dentry = inode->i_private;
+ if (!dentry)
+ return -EINVAL;
+
+ elist = __seq_open_private(file, seq_ops, sizeof(*elist));
+ if (!elist)
+ return -ENOMEM;
+
+ ti = get_tracefs(dentry->d_inode);
+ ei = ti->private;
+
+ /*
+ * Start off at level 0 (/sys/kernel/tracing/events)
+ * Initialize head to the events files and next to the
+ * first file.
+ */
+ elist->level = 0;
+ elist->head[0] = &ei->e_top_files;
+ elist->next[0] = ei->e_top_files.next;
+
+ return 0;
+}
+
+const struct file_operations eventfs_show_dentry_fops = {
+ .open = eventfs_show_dentry_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = seq_release,
+};
@@ -2,11 +2,55 @@
#ifndef _TRACEFS_INTERNAL_H
#define _TRACEFS_INTERNAL_H
+#include <linux/mutex.h>
+
enum {
TRACEFS_EVENT_INODE = BIT(1),
TRACEFS_EVENT_TOP_INODE = BIT(2),
};
+struct eventfs_inode {
+ struct list_head e_top_files;
+};
+
+/*
+ * struct eventfs_file - hold the properties of the eventfs files and
+ * directories.
+ * @name: the name of the file or directory to create
+ * @d_parent: holds parent's dentry
+ * @dentry: once accessed holds dentry
+ * @list: file or directory to be added to parent directory
+ * @ei: list of files and directories within directory
+ * @fop: file_operations for file or directory
+ * @iop: inode_operations for file or directory
+ * @data: something that the caller will want to get to later on
+ * @mode: the permission that the file or directory should have
+ */
+struct eventfs_file {
+ const char *name;
+ struct dentry *d_parent;
+ struct dentry *dentry;
+ struct list_head list;
+ struct eventfs_inode *ei;
+ const struct file_operations *fop;
+ const struct inode_operations *iop;
+ /*
+ * Union - used for deletion
+ * @del_list: list of eventfs_file to delete
+ * @rcu: eventfs_file to delete in RCU
+ * @is_freed: node is freed if one of the above is set
+ */
+ union {
+ struct list_head del_list;
+ struct rcu_head rcu;
+ unsigned long is_freed;
+ };
+ void *data;
+ umode_t mode;
+};
+
+extern struct mutex eventfs_mutex;
+
struct tracefs_inode {
unsigned long flags;
void *private;
@@ -58,6 +58,8 @@ struct dentry *tracefs_create_instance_dir(const char *name, struct dentry *pare
bool tracefs_initialized(void);
+extern const struct file_operations eventfs_show_dentry_fops;
+
#endif /* CONFIG_TRACING */
#endif
@@ -3639,6 +3639,9 @@ create_event_toplevel_files(struct dentry *parent, struct trace_array *tr)
return -ENOMEM;
}
+ trace_create_file("show_events_dentries", TRACE_MODE_READ, parent, d_events,
+ &eventfs_show_dentry_fops);
+
error = eventfs_add_events_file("enable", TRACE_MODE_WRITE, d_events,
tr, &ftrace_tr_enable_fops);
if (error)