[RFC PATCH v2 5/7] tracing/user_events: Register with trace namespace API

From: Beau Belgrave
Date: Thu Jul 28 2022 - 19:53:04 EST


Register user_events up to the trace namespace API to allow user
programs to interface with isolated events when required. Each namespace
will have their own user_events_status and user_events_data files that
have the same ABI as before, however, the system name for events created
will be different (user_events.<namespace_name> vs user_events).

Signed-off-by: Beau Belgrave <beaub@xxxxxxxxxxxxxxxxxxx>
---
kernel/trace/trace_events_user.c | 167 ++++++++++++++++++++++++++++++-
1 file changed, 166 insertions(+), 1 deletion(-)

diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c
index 44f9efd58af5..9694eee27956 100644
--- a/kernel/trace/trace_events_user.c
+++ b/kernel/trace/trace_events_user.c
@@ -23,6 +23,10 @@
#include "trace.h"
#include "trace_dynevent.h"

+#ifdef CONFIG_TRACE_NAMESPACE
+#include "trace_namespace.h"
+#endif
+
#define USER_EVENTS_PREFIX_LEN (sizeof(USER_EVENTS_PREFIX)-1)

#define FIELD_DEPTH_TYPE 0
@@ -180,6 +184,18 @@ static void user_event_group_destroy(struct user_event_group *group)
kfree(group);
}

+static void user_event_group_unlink(struct user_event_group *group)
+{
+ if (WARN_ON(refcount_read(&group->refcnt) != 1))
+ pr_warn("user_event: Group unlink with more than 1 ref\n");
+
+ mutex_lock(&group_mutex);
+ hash_del(&group->node);
+ mutex_unlock(&group_mutex);
+
+ user_event_group_destroy(group);
+}
+
static char *user_event_group_system_name(const char *name)
{
char *system_name;
@@ -262,6 +278,7 @@ static struct user_event_group *user_event_group_create(const char *name,

return group;
error:
+ /* Hash table not added, safe to destroy vs unlink */
if (group)
user_event_group_destroy(group);

@@ -1905,6 +1922,148 @@ static int create_user_tracefs(struct dentry *parent,
return -ENODEV;
}

+#ifdef CONFIG_TRACE_NAMESPACE
+static int user_event_ns_create(struct trace_namespace *ns)
+{
+ struct user_event_group *group;
+ int ret;
+
+ group = user_event_group_create(ns->name, ns->id);
+
+ if (!group)
+ return -ENOMEM;
+
+ ret = create_user_tracefs(ns->dir, group);
+
+ user_event_group_release(group);
+
+ if (ret) {
+ user_event_group_unlink(group);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int user_event_ns_remove(struct trace_namespace *ns)
+{
+ struct user_event_group *group = user_event_group_find(ns->id);
+ struct user_event *user;
+ struct hlist_node *tmp;
+ int i, ret = 0;
+
+ if (!group)
+ return -ENOENT;
+
+ /*
+ * Lock out finding this namespace while we are doing this so that
+ * user programs trying to open a file owned by this group will block
+ * until we are done here. The user program upon unblocking will then
+ * fail to find the group if we removed it.
+ */
+ mutex_lock(&group_mutex);
+
+ /* Ensure we have the only reference */
+ if (refcount_read(&group->refcnt) != 2) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ /*
+ * At this point no more files can be opened by user space programs
+ * while we are holding the group_mutex (they'll block on group_mutex).
+ * To ensure other parts of the kernel aren't registering something we
+ * also grab the group register mutex as an extra precaution.
+ *
+ * The events might be being recorded, which will result in their
+ * being busy and we'll bail out.
+ *
+ * NOTE: event_mutex is held, locking reg_mutex could deadlock so we
+ * must try to lock it and treat as busy if we cannot.
+ */
+ if (!mutex_trylock(&group->reg_mutex)) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ hash_for_each_safe(group->register_table, i, tmp, user, node) {
+ if (!user_event_last_ref(user)) {
+ ret = -EBUSY;
+ break;
+ }
+
+ ret = destroy_user_event(user);
+
+ if (ret)
+ break;
+ }
+
+ mutex_unlock(&group->reg_mutex);
+out:
+ mutex_unlock(&group_mutex);
+
+ user_event_group_release(group);
+
+ if (!ret)
+ user_event_group_unlink(group);
+
+ return ret;
+}
+
+static int user_event_ns_parse(struct trace_namespace *ns, const char *command)
+{
+ return -ECANCELED;
+}
+
+static int user_event_ns_show(struct trace_namespace *ns, struct seq_file *m)
+{
+ return 0;
+}
+
+static bool user_event_ns_is_busy(struct trace_namespace *ns)
+{
+ struct user_event_group *group = user_event_group_find(ns->id);
+ struct user_event *user;
+ int i;
+ bool busy = false;
+
+ if (!group)
+ return false;
+
+ /*
+ * Quick check to ensure all events aren't busy:
+ * The actual remove will do a more exhaustive check including
+ * finding if any outstanding files are opened, etc.
+ *
+ * NOTE: event_mutex is held, locking reg_mutex could deadlock so we
+ * must try to lock it and treat as busy if we cannot.
+ */
+ if (!mutex_trylock(&group->reg_mutex))
+ return true;
+
+ hash_for_each(group->register_table, i, user, node) {
+ if (!user_event_last_ref(user)) {
+ busy = true;
+ break;
+ }
+ }
+
+ mutex_unlock(&group->reg_mutex);
+
+ user_event_group_release(group);
+
+ return busy;
+}
+
+static struct trace_namespace_operations user_event_ns_ops = {
+ .create = user_event_ns_create,
+ .remove = user_event_ns_remove,
+ .parse = user_event_ns_parse,
+ .show = user_event_ns_show,
+ .is_busy = user_event_ns_is_busy,
+};
+#endif
+
static int __init trace_events_user_init(void)
{
int ret;
@@ -1918,7 +2077,8 @@ static int __init trace_events_user_init(void)

if (ret) {
pr_warn("user_events could not register with tracefs\n");
- user_event_group_destroy(root_group);
+ user_event_group_release(root_group);
+ user_event_group_unlink(root_group);
root_group = NULL;
return ret;
}
@@ -1926,6 +2086,11 @@ static int __init trace_events_user_init(void)
if (dyn_event_register(&user_event_dops))
pr_warn("user_events could not register with dyn_events\n");

+#ifdef CONFIG_TRACE_NAMESPACE
+ if (trace_namespace_register(&user_event_ns_ops))
+ pr_warn("user_events could not register with namespaces\n");
+#endif
+
return 0;
}

--
2.25.1