summaryrefslogtreecommitdiffstats
path: root/src/tracefs-events.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/tracefs-events.c')
-rw-r--r--src/tracefs-events.c1515
1 files changed, 1515 insertions, 0 deletions
diff --git a/src/tracefs-events.c b/src/tracefs-events.c
new file mode 100644
index 0000000..c2adf41
--- /dev/null
+++ b/src/tracefs-events.c
@@ -0,0 +1,1515 @@
+// SPDX-License-Identifier: LGPL-2.1
+/*
+ * Copyright (C) 2008, 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ *
+ * Updates:
+ * Copyright (C) 2019, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com>
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include <kbuffer.h>
+
+#include "tracefs.h"
+#include "tracefs-local.h"
+
+static struct follow_event *root_followers;
+static int nr_root_followers;
+
+static struct follow_event *root_missed_followers;
+static int nr_root_missed_followers;
+
+struct cpu_iterate {
+ struct tracefs_cpu *tcpu;
+ struct tep_record record;
+ struct tep_event *event;
+ struct kbuffer *kbuf;
+ void *page;
+ int psize;
+ int cpu;
+};
+
+static int read_kbuf_record(struct cpu_iterate *cpu)
+{
+ unsigned long long ts;
+ void *ptr;
+
+ if (!cpu || !cpu->kbuf)
+ return -1;
+ ptr = kbuffer_read_event(cpu->kbuf, &ts);
+ if (!ptr)
+ return -1;
+
+ memset(&cpu->record, 0, sizeof(cpu->record));
+ cpu->record.ts = ts;
+ cpu->record.size = kbuffer_event_size(cpu->kbuf);
+ cpu->record.record_size = kbuffer_curr_size(cpu->kbuf);
+ cpu->record.missed_events = kbuffer_missed_events(cpu->kbuf);
+ cpu->record.cpu = cpu->cpu;
+ cpu->record.data = ptr;
+ cpu->record.ref_count = 1;
+
+ kbuffer_next_event(cpu->kbuf, NULL);
+
+ return 0;
+}
+
+int read_next_page(struct tep_handle *tep, struct cpu_iterate *cpu)
+{
+ enum kbuffer_long_size long_size;
+ enum kbuffer_endian endian;
+ int r;
+
+ if (!cpu->tcpu)
+ return -1;
+
+ r = tracefs_cpu_buffered_read(cpu->tcpu, cpu->page, true);
+ /*
+ * tracefs_cpu_buffered_read() only reads in full subbuffer size,
+ * but this wants partial buffers as well. If the function returns
+ * empty (-1 for EAGAIN), try tracefs_cpu_read() next, as that can
+ * read partially filled buffers too, but isn't as efficient.
+ */
+ if (r <= 0)
+ r = tracefs_cpu_read(cpu->tcpu, cpu->page, true);
+ if (r <= 0)
+ return -1;
+
+ if (!cpu->kbuf) {
+ if (tep_is_file_bigendian(tep))
+ endian = KBUFFER_ENDIAN_BIG;
+ else
+ endian = KBUFFER_ENDIAN_LITTLE;
+
+ if (tep_get_header_page_size(tep) == 8)
+ long_size = KBUFFER_LSIZE_8;
+ else
+ long_size = KBUFFER_LSIZE_4;
+
+ cpu->kbuf = kbuffer_alloc(long_size, endian);
+ if (!cpu->kbuf)
+ return -1;
+ }
+
+ kbuffer_load_subbuffer(cpu->kbuf, cpu->page);
+ if (kbuffer_subbuffer_size(cpu->kbuf) > r) {
+ tracefs_warning("%s: page_size > %d", __func__, r);
+ return -1;
+ }
+
+ return 0;
+}
+
+int read_next_record(struct tep_handle *tep, struct cpu_iterate *cpu)
+{
+ int id;
+
+ do {
+ while (!read_kbuf_record(cpu)) {
+ id = tep_data_type(tep, &(cpu->record));
+ cpu->event = tep_find_event(tep, id);
+ if (cpu->event)
+ return 0;
+ }
+ } while (!read_next_page(tep, cpu));
+
+ return -1;
+}
+
+/**
+ * tracefs_follow_missed_events - Add callback for missed events for iterators
+ * @instance: The instance to follow
+ * @callback: The function to call when missed events is detected
+ * @callback_data: The data to pass to @callback
+ *
+ * This attaches a callback to an @instance or the root instance if @instance
+ * is NULL, where if tracefs_iterate_raw_events() is called, that if missed
+ * events are detected, it will call @callback, with the following parameters:
+ * @event: The event pointer of the record with the missing events
+ * @record; The event instance of @event.
+ * @cpu: The cpu that the event happened on.
+ * @callback_data: The same as @callback_data passed to the function.
+ *
+ * If the count of missing events is available, @record->missed_events
+ * will have a positive number holding the number of missed events since
+ * the last event on the same CPU, or just -1 if that number is unknown
+ * but missed events did happen.
+ *
+ * Returns 0 on success and -1 on error.
+ */
+int tracefs_follow_missed_events(struct tracefs_instance *instance,
+ int (*callback)(struct tep_event *,
+ struct tep_record *,
+ int, void *),
+ void *callback_data)
+{
+ struct follow_event **followers;
+ struct follow_event *follower;
+ struct follow_event follow;
+ int *nr_followers;
+
+ follow.event = NULL;
+ follow.callback = callback;
+ follow.callback_data = callback_data;
+
+ if (instance) {
+ followers = &instance->missed_followers;
+ nr_followers = &instance->nr_missed_followers;
+ } else {
+ followers = &root_missed_followers;
+ nr_followers = &nr_root_missed_followers;
+ }
+ follower = realloc(*followers, sizeof(*follower) *
+ ((*nr_followers) + 1));
+ if (!follower)
+ return -1;
+
+ *followers = follower;
+ follower[(*nr_followers)++] = follow;
+
+ return 0;
+}
+
+static int call_missed_events(struct tracefs_instance *instance,
+ struct tep_event *event, struct tep_record *record, int cpu)
+{
+ struct follow_event *followers;
+ int nr_followers;
+ int ret = 0;
+ int i;
+
+ if (instance) {
+ followers = instance->missed_followers;
+ nr_followers = instance->nr_missed_followers;
+ } else {
+ followers = root_missed_followers;
+ nr_followers = nr_root_missed_followers;
+ }
+
+ if (!followers)
+ return 0;
+
+ for (i = 0; i < nr_followers; i++) {
+ ret |= followers[i].callback(event, record,
+ cpu, followers[i].callback_data);
+ }
+
+ return ret;
+}
+
+static int call_followers(struct tracefs_instance *instance,
+ struct tep_event *event, struct tep_record *record, int cpu)
+{
+ struct follow_event *followers;
+ int nr_followers;
+ int ret = 0;
+ int i;
+
+ if (record->missed_events)
+ ret = call_missed_events(instance, event, record, cpu);
+ if (ret)
+ return ret;
+
+ if (instance) {
+ followers = instance->followers;
+ nr_followers = instance->nr_followers;
+ } else {
+ followers = root_followers;
+ nr_followers = nr_root_followers;
+ }
+
+ if (!followers)
+ return 0;
+
+ for (i = 0; i < nr_followers; i++) {
+ if (followers[i].event == event)
+ ret |= followers[i].callback(event, record,
+ cpu, followers[i].callback_data);
+ }
+
+ return ret;
+}
+
+static int read_cpu_pages(struct tep_handle *tep, struct tracefs_instance *instance,
+ struct cpu_iterate *cpus, int count,
+ int (*callback)(struct tep_event *,
+ struct tep_record *,
+ int, void *),
+ void *callback_context,
+ bool *keep_going)
+{
+ bool has_data = false;
+ int ret;
+ int i, j;
+
+ for (i = 0; i < count; i++) {
+ ret = read_next_record(tep, cpus + i);
+ if (!ret)
+ has_data = true;
+ }
+
+ while (has_data && *(volatile bool *)keep_going) {
+ j = count;
+ for (i = 0; i < count; i++) {
+ if (!cpus[i].event)
+ continue;
+ if (j == count || cpus[j].record.ts > cpus[i].record.ts)
+ j = i;
+ }
+ if (j < count) {
+ if (call_followers(instance, cpus[j].event, &cpus[j].record, cpus[j].cpu))
+ break;
+ if (callback &&
+ callback(cpus[j].event, &cpus[j].record, cpus[j].cpu, callback_context))
+ break;
+ cpus[j].event = NULL;
+ read_next_record(tep, cpus + j);
+ } else {
+ has_data = false;
+ }
+ }
+
+ return 0;
+}
+
+static int open_cpu_files(struct tracefs_instance *instance, cpu_set_t *cpus,
+ int cpu_size, struct cpu_iterate **all_cpus, int *count)
+{
+ struct tracefs_cpu *tcpu;
+ struct cpu_iterate *tmp;
+ int nr_cpus;
+ int cpu;
+ int i = 0;
+
+ *all_cpus = NULL;
+
+ nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
+ for (cpu = 0; cpu < nr_cpus; cpu++) {
+ if (cpus && !CPU_ISSET_S(cpu, cpu_size, cpus))
+ continue;
+ tcpu = tracefs_cpu_open(instance, cpu, true);
+ tmp = realloc(*all_cpus, (i + 1) * sizeof(*tmp));
+ if (!tmp) {
+ i--;
+ goto error;
+ }
+
+ *all_cpus = tmp;
+
+ memset(tmp + i, 0, sizeof(*tmp));
+
+ if (!tcpu)
+ goto error;
+
+ tmp[i].tcpu = tcpu;
+ tmp[i].cpu = cpu;
+ tmp[i].psize = tracefs_cpu_read_size(tcpu);
+ tmp[i].page = malloc(tmp[i].psize);
+
+ if (!tmp[i++].page)
+ goto error;
+ }
+ *count = i;
+ return 0;
+ error:
+ tmp = *all_cpus;
+ for (; i >= 0; i--) {
+ tracefs_cpu_close(tmp[i].tcpu);
+ free(tmp[i].page);
+ }
+ free(tmp);
+ *all_cpus = NULL;
+ return -1;
+}
+
+/**
+ * tracefs_follow_event - Add callback for specific events for iterators
+ * @tep: a handle to the trace event parser context
+ * @instance: The instance to follow
+ * @system: The system of the event to track
+ * @event_name: The name of the event to track
+ * @callback: The function to call when the event is hit in an iterator
+ * @callback_data: The data to pass to @callback
+ *
+ * This attaches a callback to an @instance or the root instance if @instance
+ * is NULL, where if tracefs_iterate_raw_events() is called, that if the specified
+ * event is hit, it will call @callback, with the following parameters:
+ * @event: The event pointer that was found by @system and @event_name.
+ * @record; The event instance of @event.
+ * @cpu: The cpu that the event happened on.
+ * @callback_data: The same as @callback_data passed to the function.
+ *
+ * Returns 0 on success and -1 on error.
+ */
+int tracefs_follow_event(struct tep_handle *tep, struct tracefs_instance *instance,
+ const char *system, const char *event_name,
+ int (*callback)(struct tep_event *,
+ struct tep_record *,
+ int, void *),
+ void *callback_data)
+{
+ struct follow_event **followers;
+ struct follow_event *follower;
+ struct follow_event follow;
+ int *nr_followers;
+
+ if (!tep) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ follow.event = tep_find_event_by_name(tep, system, event_name);
+ if (!follow.event) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ follow.callback = callback;
+ follow.callback_data = callback_data;
+
+ if (instance) {
+ followers = &instance->followers;
+ nr_followers = &instance->nr_followers;
+ } else {
+ followers = &root_followers;
+ nr_followers = &nr_root_followers;
+ }
+ follower = realloc(*followers, sizeof(*follower) *
+ ((*nr_followers) + 1));
+ if (!follower)
+ return -1;
+
+ *followers = follower;
+ follower[(*nr_followers)++] = follow;
+
+ return 0;
+}
+
+static bool top_iterate_keep_going;
+
+/*
+ * tracefs_iterate_raw_events - Iterate through events in trace_pipe_raw,
+ * per CPU trace buffers
+ * @tep: a handle to the trace event parser context
+ * @instance: ftrace instance, can be NULL for the top instance
+ * @cpus: Iterate only through the buffers of CPUs, set in the mask.
+ * If NULL, iterate through all CPUs.
+ * @cpu_size: size of @cpus set
+ * @callback: A user function, called for each record from the file
+ * @callback_context: A custom context, passed to the user callback function
+ *
+ * If the @callback returns non-zero, the iteration stops - in that case all
+ * records from the current page will be lost from future reads
+ * The events are iterated in sorted order, oldest first.
+ *
+ * Returns -1 in case of an error, or 0 otherwise
+ */
+int tracefs_iterate_raw_events(struct tep_handle *tep,
+ struct tracefs_instance *instance,
+ cpu_set_t *cpus, int cpu_size,
+ int (*callback)(struct tep_event *,
+ struct tep_record *,
+ int, void *),
+ void *callback_context)
+{
+ bool *keep_going = instance ? &instance->iterate_keep_going :
+ &top_iterate_keep_going;
+ struct follow_event *followers;
+ struct cpu_iterate *all_cpus;
+ int count = 0;
+ int ret;
+ int i;
+
+ (*(volatile bool *)keep_going) = true;
+
+ if (!tep)
+ return -1;
+
+ if (instance)
+ followers = instance->followers;
+ else
+ followers = root_followers;
+ if (!callback && !followers)
+ return -1;
+
+ ret = open_cpu_files(instance, cpus, cpu_size, &all_cpus, &count);
+ if (ret < 0)
+ goto out;
+ ret = read_cpu_pages(tep, instance, all_cpus, count,
+ callback, callback_context,
+ keep_going);
+
+out:
+ if (all_cpus) {
+ for (i = 0; i < count; i++) {
+ kbuffer_free(all_cpus[i].kbuf);
+ tracefs_cpu_close(all_cpus[i].tcpu);
+ free(all_cpus[i].page);
+ }
+ free(all_cpus);
+ }
+
+ return ret;
+}
+
+/**
+ * tracefs_iterate_stop - stop the iteration over the raw events.
+ * @instance: ftrace instance, can be NULL for top tracing instance.
+ */
+void tracefs_iterate_stop(struct tracefs_instance *instance)
+{
+ if (instance)
+ instance->iterate_keep_going = false;
+ else
+ top_iterate_keep_going = false;
+}
+
+static int add_list_string(char ***list, const char *name)
+{
+ char **tmp;
+
+ tmp = tracefs_list_add(*list, name);
+ if (!tmp) {
+ tracefs_list_free(*list);
+ *list = NULL;
+ return -1;
+ }
+
+ *list = tmp;
+ return 0;
+}
+
+__hidden char *trace_append_file(const char *dir, const char *name)
+{
+ char *file;
+ int ret;
+
+ ret = asprintf(&file, "%s/%s", dir, name);
+
+ return ret < 0 ? NULL : file;
+}
+
+static int event_file(char **path, const char *system,
+ const char *event, const char *file)
+{
+ if (!system || !event || !file)
+ return -1;
+
+ return asprintf(path, "events/%s/%s/%s",
+ system, event, file);
+}
+
+/**
+ * tracefs_event_get_file - return a file in an event directory
+ * @instance: The instance the event is in (NULL for top level)
+ * @system: The system name that the event file is in
+ * @event: The event name of the event
+ * @file: The name of the file in the event directory.
+ *
+ * Returns a path to a file in the event director.
+ * or NULL on error. The path returned must be freed with
+ * tracefs_put_tracing_file().
+ */
+char *tracefs_event_get_file(struct tracefs_instance *instance,
+ const char *system, const char *event,
+ const char *file)
+{
+ char *instance_path;
+ char *path;
+ int ret;
+
+ ret = event_file(&path, system, event, file);
+ if (ret < 0)
+ return NULL;
+
+ instance_path = tracefs_instance_get_file(instance, path);
+ free(path);
+
+ return instance_path;
+}
+
+/**
+ * tracefs_event_file_read - read the content from an event file
+ * @instance: The instance the event is in (NULL for top level)
+ * @system: The system name that the event file is in
+ * @event: The event name of the event
+ * @file: The name of the file in the event directory.
+ * @psize: the size of the content read.
+ *
+ * Reads the content of the event file that is passed via the
+ * arguments and returns the content.
+ *
+ * Return a string containing the content of the file or NULL
+ * on error. The string returned must be freed with free().
+ */
+char *tracefs_event_file_read(struct tracefs_instance *instance,
+ const char *system, const char *event,
+ const char *file, int *psize)
+{
+ char *content;
+ char *path;
+ int ret;
+
+ ret = event_file(&path, system, event, file);
+ if (ret < 0)
+ return NULL;
+
+ content = tracefs_instance_file_read(instance, path, psize);
+ free(path);
+ return content;
+}
+
+/**
+ * tracefs_event_file_write - write to an event file
+ * @instance: The instance the event is in (NULL for top level)
+ * @system: The system name that the event file is in
+ * @event: The event name of the event
+ * @file: The name of the file in the event directory.
+ * @str: The string to write into the file
+ *
+ * Writes the content of @str to a file in the instance directory.
+ * The content of the file will be overwritten by @str.
+ *
+ * Return 0 on success, and -1 on error.
+ */
+int tracefs_event_file_write(struct tracefs_instance *instance,
+ const char *system, const char *event,
+ const char *file, const char *str)
+{
+ char *path;
+ int ret;
+
+ ret = event_file(&path, system, event, file);
+ if (ret < 0)
+ return -1;
+
+ ret = tracefs_instance_file_write(instance, path, str);
+ free(path);
+ return ret;
+}
+
+/**
+ * tracefs_event_file_append - write to an event file
+ * @instance: The instance the event is in (NULL for top level)
+ * @system: The system name that the event file is in
+ * @event: The event name of the event
+ * @file: The name of the file in the event directory.
+ * @str: The string to write into the file
+ *
+ * Writes the content of @str to a file in the instance directory.
+ * The content of @str will be appended to the content of the file.
+ * The current content should not be lost.
+ *
+ * Return 0 on success, and -1 on error.
+ */
+int tracefs_event_file_append(struct tracefs_instance *instance,
+ const char *system, const char *event,
+ const char *file, const char *str)
+{
+ char *path;
+ int ret;
+
+ ret = event_file(&path, system, event, file);
+ if (ret < 0)
+ return -1;
+
+ ret = tracefs_instance_file_append(instance, path, str);
+ free(path);
+ return ret;
+}
+
+/**
+ * tracefs_event_file_clear - clear an event file
+ * @instance: The instance the event is in (NULL for top level)
+ * @system: The system name that the event file is in
+ * @event: The event name of the event
+ * @file: The name of the file in the event directory.
+ *
+ * Clears the content of the event file. That is, it is opened
+ * with O_TRUNC and then closed.
+ *
+ * Return 0 on success, and -1 on error.
+ */
+int tracefs_event_file_clear(struct tracefs_instance *instance,
+ const char *system, const char *event,
+ const char *file)
+{
+ char *path;
+ int ret;
+
+ ret = event_file(&path, system, event, file);
+ if (ret < 0)
+ return -1;
+
+ ret = tracefs_instance_file_clear(instance, path);
+ free(path);
+ return ret;
+}
+
+/**
+ * tracefs_event_file_exits - test if a file exists
+ * @instance: The instance the event is in (NULL for top level)
+ * @system: The system name that the event file is in
+ * @event: The event name of the event
+ * @file: The name of the file in the event directory.
+ *
+ * Return true if the file exists, false if it odes not or
+ * an error occurred.
+ */
+bool tracefs_event_file_exists(struct tracefs_instance *instance,
+ const char *system, const char *event,
+ const char *file)
+{
+ char *path;
+ bool ret;
+
+ if (event_file(&path, system, event, file) < 0)
+ return false;
+
+ ret = tracefs_file_exists(instance, path);
+ free(path);
+ return ret;
+}
+
+/**
+ * tracefs_event_systems - return list of systems for tracing
+ * @tracing_dir: directory holding the "events" directory
+ * if NULL, top tracing directory is used
+ *
+ * Returns an allocated list of system names. Both the names and
+ * the list must be freed with tracefs_list_free()
+ * The list returned ends with a "NULL" pointer
+ */
+char **tracefs_event_systems(const char *tracing_dir)
+{
+ struct dirent *dent;
+ char **systems = NULL;
+ char *events_dir;
+ struct stat st;
+ DIR *dir;
+ int ret;
+
+ if (!tracing_dir)
+ tracing_dir = tracefs_tracing_dir();
+
+ if (!tracing_dir)
+ return NULL;
+
+ events_dir = trace_append_file(tracing_dir, "events");
+ if (!events_dir)
+ return NULL;
+
+ /*
+ * Search all the directories in the events directory,
+ * and collect the ones that have the "enable" file.
+ */
+ ret = stat(events_dir, &st);
+ if (ret < 0 || !S_ISDIR(st.st_mode))
+ goto out_free;
+
+ dir = opendir(events_dir);
+ if (!dir)
+ goto out_free;
+
+ while ((dent = readdir(dir))) {
+ const char *name = dent->d_name;
+ char *enable;
+ char *sys;
+
+ if (strcmp(name, ".") == 0 ||
+ strcmp(name, "..") == 0)
+ continue;
+
+ sys = trace_append_file(events_dir, name);
+ ret = stat(sys, &st);
+ if (ret < 0 || !S_ISDIR(st.st_mode)) {
+ free(sys);
+ continue;
+ }
+
+ enable = trace_append_file(sys, "enable");
+
+ ret = stat(enable, &st);
+ if (ret >= 0) {
+ if (add_list_string(&systems, name) < 0)
+ goto out_free;
+ }
+ free(enable);
+ free(sys);
+ }
+
+ closedir(dir);
+
+ out_free:
+ free(events_dir);
+ return systems;
+}
+
+/**
+ * tracefs_system_events - return list of events for system
+ * @tracing_dir: directory holding the "events" directory
+ * @system: the system to return the events for
+ *
+ * Returns an allocated list of event names. Both the names and
+ * the list must be freed with tracefs_list_free()
+ * The list returned ends with a "NULL" pointer
+ */
+char **tracefs_system_events(const char *tracing_dir, const char *system)
+{
+ struct dirent *dent;
+ char **events = NULL;
+ char *system_dir = NULL;
+ struct stat st;
+ DIR *dir;
+ int ret;
+
+ if (!tracing_dir)
+ tracing_dir = tracefs_tracing_dir();
+
+ if (!tracing_dir || !system)
+ return NULL;
+
+ asprintf(&system_dir, "%s/events/%s", tracing_dir, system);
+ if (!system_dir)
+ return NULL;
+
+ ret = stat(system_dir, &st);
+ if (ret < 0 || !S_ISDIR(st.st_mode))
+ goto out_free;
+
+ dir = opendir(system_dir);
+ if (!dir)
+ goto out_free;
+
+ while ((dent = readdir(dir))) {
+ const char *name = dent->d_name;
+ char *event;
+
+ if (strcmp(name, ".") == 0 ||
+ strcmp(name, "..") == 0)
+ continue;
+
+ event = trace_append_file(system_dir, name);
+ ret = stat(event, &st);
+ if (ret < 0 || !S_ISDIR(st.st_mode)) {
+ free(event);
+ continue;
+ }
+
+ if (add_list_string(&events, name) < 0)
+ goto out_free;
+
+ free(event);
+ }
+
+ closedir(dir);
+
+ out_free:
+ free(system_dir);
+
+ return events;
+}
+
+/**
+ * tracefs_tracers - returns an array of available tracers
+ * @tracing_dir: The directory that contains the tracing directory
+ *
+ * Returns an allocate list of plugins. The array ends with NULL
+ * Both the plugin names and array must be freed with tracefs_list_free()
+ */
+char **tracefs_tracers(const char *tracing_dir)
+{
+ char *available_tracers;
+ struct stat st;
+ char **plugins = NULL;
+ char *buf;
+ char *str, *saveptr;
+ char *plugin;
+ int slen;
+ int len;
+ int ret;
+
+ if (!tracing_dir)
+ tracing_dir = tracefs_tracing_dir();
+
+ if (!tracing_dir)
+ return NULL;
+
+ available_tracers = trace_append_file(tracing_dir, "available_tracers");
+ if (!available_tracers)
+ return NULL;
+
+ ret = stat(available_tracers, &st);
+ if (ret < 0)
+ goto out_free;
+
+ len = str_read_file(available_tracers, &buf, true);
+ if (len <= 0)
+ goto out_free;
+
+ for (str = buf; ; str = NULL) {
+ plugin = strtok_r(str, " ", &saveptr);
+ if (!plugin)
+ break;
+ slen = strlen(plugin);
+ if (!slen)
+ continue;
+
+ /* chop off any newlines */
+ if (plugin[slen - 1] == '\n')
+ plugin[slen - 1] = '\0';
+
+ /* Skip the non tracers */
+ if (strcmp(plugin, "nop") == 0 ||
+ strcmp(plugin, "none") == 0)
+ continue;
+
+ if (add_list_string(&plugins, plugin) < 0)
+ break;
+ }
+ free(buf);
+
+ out_free:
+ free(available_tracers);
+
+ return plugins;
+}
+
+static int load_events(struct tep_handle *tep,
+ const char *tracing_dir, const char *system, bool check)
+{
+ int ret = 0, failure = 0;
+ char **events = NULL;
+ struct stat st;
+ int len = 0;
+ int i;
+
+ if (!tracing_dir)
+ tracing_dir = tracefs_tracing_dir();
+
+ events = tracefs_system_events(tracing_dir, system);
+ if (!events)
+ return -ENOENT;
+
+ for (i = 0; events[i]; i++) {
+ char *format;
+ char *buf;
+
+ ret = asprintf(&format, "%s/events/%s/%s/format",
+ tracing_dir, system, events[i]);
+ if (ret < 0) {
+ failure = -ENOMEM;
+ break;
+ }
+
+ ret = stat(format, &st);
+ if (ret < 0)
+ goto next_event;
+
+ /* check if event is already added, to avoid duplicates */
+ if (check && tep_find_event_by_name(tep, system, events[i]))
+ goto next_event;
+
+ len = str_read_file(format, &buf, true);
+ if (len <= 0)
+ goto next_event;
+
+ ret = tep_parse_event(tep, buf, len, system);
+ free(buf);
+next_event:
+ free(format);
+ if (ret)
+ failure = ret;
+ }
+
+ tracefs_list_free(events);
+ return failure;
+}
+
+__hidden int trace_rescan_events(struct tep_handle *tep,
+ const char *tracing_dir, const char *system)
+{
+ /* ToDo: add here logic for deleting removed events from tep handle */
+ return load_events(tep, tracing_dir, system, true);
+}
+
+__hidden int trace_load_events(struct tep_handle *tep,
+ const char *tracing_dir, const char *system)
+{
+ return load_events(tep, tracing_dir, system, false);
+}
+
+__hidden struct tep_event *get_tep_event(struct tep_handle *tep,
+ const char *system, const char *name)
+{
+ struct tep_event *event;
+
+ /* Check if event exists in the system */
+ if (!tracefs_event_file_exists(NULL, system, name, "format"))
+ return NULL;
+
+ /* If the event is already loaded in the tep, return it */
+ event = tep_find_event_by_name(tep, system, name);
+ if (event)
+ return event;
+
+ /* Try to load any new events from the given system */
+ if (trace_rescan_events(tep, NULL, system))
+ return NULL;
+
+ return tep_find_event_by_name(tep, system, name);
+}
+
+static int read_header(struct tep_handle *tep, const char *tracing_dir)
+{
+ struct stat st;
+ char *header;
+ char *buf;
+ int len;
+ int ret = -1;
+
+ header = trace_append_file(tracing_dir, "events/header_page");
+
+ ret = stat(header, &st);
+ if (ret < 0)
+ goto out;
+
+ len = str_read_file(header, &buf, true);
+ if (len <= 0)
+ goto out;
+
+ tep_parse_header_page(tep, buf, len, sizeof(long));
+
+ free(buf);
+
+ ret = 0;
+ out:
+ free(header);
+ return ret;
+}
+
+static bool contains(const char *name, const char * const *names)
+{
+ if (!names)
+ return false;
+ for (; *names; names++)
+ if (strcmp(name, *names) == 0)
+ return true;
+ return false;
+}
+
+static void load_kallsyms(struct tep_handle *tep)
+{
+ char *buf;
+
+ if (str_read_file("/proc/kallsyms", &buf, false) <= 0)
+ return;
+
+ tep_parse_kallsyms(tep, buf);
+ free(buf);
+}
+
+static int load_saved_cmdlines(const char *tracing_dir,
+ struct tep_handle *tep, bool warn)
+{
+ char *path;
+ char *buf;
+ int ret;
+
+ path = trace_append_file(tracing_dir, "saved_cmdlines");
+ if (!path)
+ return -1;
+
+ ret = str_read_file(path, &buf, false);
+ free(path);
+ if (ret <= 0)
+ return -1;
+
+ ret = tep_parse_saved_cmdlines(tep, buf);
+ free(buf);
+
+ return ret;
+}
+
+static void load_printk_formats(const char *tracing_dir,
+ struct tep_handle *tep)
+{
+ char *path;
+ char *buf;
+ int ret;
+
+ path = trace_append_file(tracing_dir, "printk_formats");
+ if (!path)
+ return;
+
+ ret = str_read_file(path, &buf, false);
+ free(path);
+ if (ret <= 0)
+ return;
+
+ tep_parse_printk_formats(tep, buf);
+ free(buf);
+}
+
+/*
+ * Do a best effort attempt to load kallsyms, saved_cmdlines and
+ * printk_formats. If they can not be loaded, then this will not
+ * do the mappings. But this does not fail the loading of events.
+ */
+static void load_mappings(const char *tracing_dir,
+ struct tep_handle *tep)
+{
+ load_kallsyms(tep);
+
+ /* If there's no tracing_dir no reason to go further */
+ if (!tracing_dir)
+ tracing_dir = tracefs_tracing_dir();
+
+ if (!tracing_dir)
+ return;
+
+ load_saved_cmdlines(tracing_dir, tep, false);
+ load_printk_formats(tracing_dir, tep);
+}
+
+int tracefs_load_cmdlines(const char *tracing_dir, struct tep_handle *tep)
+{
+
+ if (!tracing_dir)
+ tracing_dir = tracefs_tracing_dir();
+
+ if (!tracing_dir)
+ return -1;
+
+ return load_saved_cmdlines(tracing_dir, tep, true);
+}
+
+static int fill_local_events_system(const char *tracing_dir,
+ struct tep_handle *tep,
+ const char * const *sys_names,
+ int *parsing_failures)
+{
+ char **systems = NULL;
+ int ret;
+ int i;
+
+ if (!tracing_dir)
+ tracing_dir = tracefs_tracing_dir();
+ if (!tracing_dir)
+ return -1;
+
+ systems = tracefs_event_systems(tracing_dir);
+ if (!systems)
+ return -1;
+
+ ret = read_header(tep, tracing_dir);
+ if (ret < 0) {
+ ret = -1;
+ goto out;
+ }
+
+ if (parsing_failures)
+ *parsing_failures = 0;
+
+ for (i = 0; systems[i]; i++) {
+ if (sys_names && !contains(systems[i], sys_names))
+ continue;
+ ret = trace_load_events(tep, tracing_dir, systems[i]);
+ if (ret && parsing_failures)
+ (*parsing_failures)++;
+ }
+
+ /* Include ftrace, as it is excluded for not having "enable" file */
+ if (!sys_names || contains("ftrace", sys_names))
+ trace_load_events(tep, tracing_dir, "ftrace");
+
+ load_mappings(tracing_dir, tep);
+
+ /* always succeed because parsing failures are not critical */
+ ret = 0;
+out:
+ tracefs_list_free(systems);
+ return ret;
+}
+
+static void set_tep_cpus(const char *tracing_dir, struct tep_handle *tep)
+{
+ struct stat st;
+ char path[PATH_MAX];
+ int cpus = sysconf(_SC_NPROCESSORS_CONF);
+ int max_cpu = 0;
+ int ret;
+ int i;
+
+ if (!tracing_dir)
+ tracing_dir = tracefs_tracing_dir();
+
+ /*
+ * Paranoid: in case sysconf() above does not work.
+ * And we also only care about the number of tracing
+ * buffers that exist. If cpus is 32, but the top half
+ * is offline, there may only be 16 tracing buffers.
+ * That's what we want to know.
+ */
+ for (i = 0; !cpus || i < cpus; i++) {
+ snprintf(path, PATH_MAX, "%s/per_cpu/cpu%d", tracing_dir, i);
+ ret = stat(path, &st);
+ if (!ret && S_ISDIR(st.st_mode))
+ max_cpu = i + 1;
+ else if (i >= cpus)
+ break;
+ }
+
+ if (!max_cpu)
+ max_cpu = cpus;
+
+ tep_set_cpus(tep, max_cpu);
+}
+
+/**
+ * tracefs_local_events_system - create a tep from the events of the specified subsystem.
+ *
+ * @tracing_dir: The directory that contains the events.
+ * @sys_name: Array of system names, to load the events from.
+ * The last element from the array must be NULL
+ *
+ * Returns a tep structure that contains the tep local to
+ * the system.
+ */
+struct tep_handle *tracefs_local_events_system(const char *tracing_dir,
+ const char * const *sys_names)
+{
+ struct tep_handle *tep = NULL;
+
+ tep = tep_alloc();
+ if (!tep)
+ return NULL;
+
+ if (fill_local_events_system(tracing_dir, tep, sys_names, NULL)) {
+ tep_free(tep);
+ tep = NULL;
+ }
+
+ set_tep_cpus(tracing_dir, tep);
+
+ /* Set the long size for this tep handle */
+ tep_set_long_size(tep, tep_get_header_page_size(tep));
+
+ return tep;
+}
+
+/**
+ * tracefs_local_events - create a tep from the events on system
+ * @tracing_dir: The directory that contains the events.
+ *
+ * Returns a tep structure that contains the teps local to
+ * the system.
+ */
+struct tep_handle *tracefs_local_events(const char *tracing_dir)
+{
+ return tracefs_local_events_system(tracing_dir, NULL);
+}
+
+/**
+ * tracefs_fill_local_events - Fill a tep with the events on system
+ * @tracing_dir: The directory that contains the events.
+ * @tep: Allocated tep handler which will be filled
+ * @parsing_failures: return number of failures while parsing the event files
+ *
+ * Returns whether the operation succeeded
+ */
+int tracefs_fill_local_events(const char *tracing_dir,
+ struct tep_handle *tep, int *parsing_failures)
+{
+ return fill_local_events_system(tracing_dir, tep,
+ NULL, parsing_failures);
+}
+
+static bool match(const char *str, regex_t *re)
+{
+ return regexec(re, str, 0, NULL, 0) == 0;
+}
+
+enum event_state {
+ STATE_INIT,
+ STATE_ENABLED,
+ STATE_DISABLED,
+ STATE_MIXED,
+ STATE_ERROR,
+};
+
+static int read_event_state(struct tracefs_instance *instance, const char *file,
+ enum event_state *state)
+{
+ char *val;
+ int ret = 0;
+
+ if (*state == STATE_ERROR)
+ return -1;
+
+ val = tracefs_instance_file_read(instance, file, NULL);
+ if (!val)
+ return -1;
+
+ switch (val[0]) {
+ case '0':
+ switch (*state) {
+ case STATE_INIT:
+ *state = STATE_DISABLED;
+ break;
+ case STATE_ENABLED:
+ *state = STATE_MIXED;
+ break;
+ default:
+ break;
+ }
+ break;
+ case '1':
+ switch (*state) {
+ case STATE_INIT:
+ *state = STATE_ENABLED;
+ break;
+ case STATE_DISABLED:
+ *state = STATE_MIXED;
+ break;
+ default:
+ break;
+ }
+ break;
+ case 'X':
+ *state = STATE_MIXED;
+ break;
+ default:
+ *state = TRACEFS_ERROR;
+ ret = -1;
+ break;
+ }
+ free(val);
+
+ return ret;
+}
+
+static int enable_disable_event(struct tracefs_instance *instance,
+ const char *system, const char *event,
+ bool enable, enum event_state *state)
+{
+ const char *str = enable ? "1" : "0";
+ char *system_event;
+ int ret;
+
+ ret = asprintf(&system_event, "events/%s/%s/enable", system, event);
+ if (ret < 0)
+ return ret;
+
+ if (state)
+ ret = read_event_state(instance, system_event, state);
+ else
+ ret = tracefs_instance_file_write(instance, system_event, str);
+ free(system_event);
+
+ return ret;
+}
+
+static int enable_disable_system(struct tracefs_instance *instance,
+ const char *system, bool enable,
+ enum event_state *state)
+{
+ const char *str = enable ? "1" : "0";
+ char *system_path;
+ int ret;
+
+ ret = asprintf(&system_path, "events/%s/enable", system);
+ if (ret < 0)
+ return ret;
+
+ if (state)
+ ret = read_event_state(instance, system_path, state);
+ else
+ ret = tracefs_instance_file_write(instance, system_path, str);
+ free(system_path);
+
+ return ret;
+}
+
+static int enable_disable_all(struct tracefs_instance *instance,
+ bool enable)
+{
+ const char *str = enable ? "1" : "0";
+ int ret;
+
+ ret = tracefs_instance_file_write(instance, "events/enable", str);
+ return ret < 0 ? ret : 0;
+}
+
+static int make_regex(regex_t *re, const char *match)
+{
+ int len = strlen(match);
+ char str[len + 3];
+ char *p = &str[0];
+
+ if (!len || match[0] != '^')
+ *(p++) = '^';
+
+ strcpy(p, match);
+ p += len;
+
+ if (!len || match[len-1] != '$')
+ *(p++) = '$';
+
+ *p = '\0';
+
+ return regcomp(re, str, REG_ICASE|REG_NOSUB);
+}
+
+static int event_enable_disable(struct tracefs_instance *instance,
+ const char *system, const char *event,
+ bool enable, enum event_state *state)
+{
+ regex_t system_re, event_re;
+ char **systems;
+ char **events = NULL;
+ int ret = -1;
+ int s, e;
+
+ /* Handle all events first */
+ if (!system && !event)
+ return enable_disable_all(instance, enable);
+
+ systems = tracefs_event_systems(NULL);
+ if (!systems)
+ goto out_free;
+
+ if (system) {
+ ret = make_regex(&system_re, system);
+ if (ret < 0)
+ goto out_free;
+ }
+ if (event) {
+ ret = make_regex(&event_re, event);
+ if (ret < 0) {
+ if (system)
+ regfree(&system_re);
+ goto out_free;
+ }
+ }
+
+ ret = -1;
+ for (s = 0; systems[s]; s++) {
+ if (system && !match(systems[s], &system_re))
+ continue;
+
+ /* Check for the short cut first */
+ if (!event) {
+ ret = enable_disable_system(instance, systems[s], enable, state);
+ if (ret < 0)
+ break;
+ ret = 0;
+ continue;
+ }
+
+ events = tracefs_system_events(NULL, systems[s]);
+ if (!events)
+ continue; /* Error? */
+
+ for (e = 0; events[e]; e++) {
+ if (!match(events[e], &event_re))
+ continue;
+ ret = enable_disable_event(instance, systems[s],
+ events[e], enable, state);
+ if (ret < 0)
+ break;
+ ret = 0;
+ }
+ tracefs_list_free(events);
+ events = NULL;
+ }
+ if (system)
+ regfree(&system_re);
+ if (event)
+ regfree(&event_re);
+
+ out_free:
+ tracefs_list_free(systems);
+ tracefs_list_free(events);
+ return ret;
+}
+
+/**
+ * tracefs_event_enable - enable specified events
+ * @instance: ftrace instance, can be NULL for the top instance
+ * @system: A regex of a system (NULL to match all systems)
+ * @event: A regex of the event in the system (NULL to match all events)
+ *
+ * This will enable events that match the @system and @event.
+ * If both @system and @event are NULL, then it will enable all events.
+ * If @system is NULL, it will look at all systems for matching events
+ * to @event.
+ * If @event is NULL, then it will enable all events in the systems
+ * that match @system.
+ *
+ * Returns 0 on success, and -1 if it encountered an error,
+ * or if no events matched. If no events matched, then -1 is set
+ * but errno will not be.
+ */
+int tracefs_event_enable(struct tracefs_instance *instance,
+ const char *system, const char *event)
+{
+ return event_enable_disable(instance, system, event, true, NULL);
+}
+
+int tracefs_event_disable(struct tracefs_instance *instance,
+ const char *system, const char *event)
+{
+ return event_enable_disable(instance, system, event, false, NULL);
+}
+
+/**
+ * tracefs_event_is_enabled - return if the event is enabled or not
+ * @instance: ftrace instance, can be NULL for the top instance
+ * @system: The name of the system to check
+ * @event: The name of the event to check
+ *
+ * Checks is an event or multiple events are enabled.
+ *
+ * If @system is NULL, then it will check all the systems where @event is
+ * a match.
+ *
+ * If @event is NULL, then it will check all events where @system is a match.
+ *
+ * If both @system and @event are NULL, then it will check all events
+ *
+ * Returns TRACEFS_ALL_ENABLED if all matching are enabled.
+ * Returns TRACEFS_SOME_ENABLED if some are enabled and some are not
+ * Returns TRACEFS_ALL_DISABLED if none of the events are enabled.
+ * Returns TRACEFS_ERROR if there is an error reading the events.
+ */
+enum tracefs_enable_state
+tracefs_event_is_enabled(struct tracefs_instance *instance,
+ const char *system, const char *event)
+{
+ enum event_state state = STATE_INIT;
+ int ret;
+
+ ret = event_enable_disable(instance, system, event, false, &state);
+
+ if (ret < 0)
+ return TRACEFS_ERROR;
+
+ switch (state) {
+ case STATE_ENABLED:
+ return TRACEFS_ALL_ENABLED;
+ case STATE_DISABLED:
+ return TRACEFS_ALL_DISABLED;
+ case STATE_MIXED:
+ return TRACEFS_SOME_ENABLED;
+ default:
+ return TRACEFS_ERROR;
+ }
+}