summaryrefslogtreecommitdiffstats
path: root/src/tracefs-tools.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/tracefs-tools.c')
-rw-r--r--src/tracefs-tools.c1273
1 files changed, 1273 insertions, 0 deletions
diff --git a/src/tracefs-tools.c b/src/tracefs-tools.c
new file mode 100644
index 0000000..8e7b46d
--- /dev/null
+++ b/src/tracefs-tools.c
@@ -0,0 +1,1273 @@
+// SPDX-License-Identifier: LGPL-2.1
+/*
+ * Copyright (C) 2008, 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ *
+ * Updates:
+ * Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com>
+ *
+ */
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <regex.h>
+#include <dirent.h>
+#include <limits.h>
+#include <pthread.h>
+#include <errno.h>
+
+#include "tracefs.h"
+#include "tracefs-local.h"
+
+__hidden pthread_mutex_t toplevel_lock = PTHREAD_MUTEX_INITIALIZER;
+
+#define TRACE_CTRL "tracing_on"
+#define TRACE_FILTER "set_ftrace_filter"
+#define TRACE_NOTRACE "set_ftrace_notrace"
+#define TRACE_FILTER_LIST "available_filter_functions"
+#define CUR_TRACER "current_tracer"
+
+#define TRACERS \
+ C(NOP, "nop"), \
+ C(CUSTOM, "CUSTOM"), \
+ C(FUNCTION, "function"), \
+ C(FUNCTION_GRAPH, "function_graph"), \
+ C(IRQSOFF, "irqsoff"), \
+ C(PREEMPTOFF, "preemptoff"), \
+ C(PREEMPTIRQSOFF, "preemptirqsoff"), \
+ C(WAKEUP, "wakeup"), \
+ C(WAKEUP_RT, "wakeup_rt"), \
+ C(WAKEUP_DL, "wakeup_dl"), \
+ C(MMIOTRACE, "mmiotrace"), \
+ C(HWLAT, "hwlat"), \
+ C(BRANCH, "branch"), \
+ C(BLOCK, "block")
+
+#undef C
+#define C(a, b) b
+const char *tracers[] = { TRACERS };
+
+#undef C
+#define C(a, b) TRACEFS_TRACER_##a
+const int tracer_enums[] = { TRACERS };
+
+/* File descriptor for Top level set_ftrace_filter */
+static int ftrace_filter_fd = -1;
+static int ftrace_notrace_fd = -1;
+
+static const char * const options_map[] = {
+ "unknown",
+ "annotate",
+ "bin",
+ "blk_cgname",
+ "blk_cgroup",
+ "blk_classic",
+ "block",
+ "context-info",
+ "disable_on_free",
+ "display-graph",
+ "event-fork",
+ "funcgraph-abstime",
+ "funcgraph-cpu",
+ "funcgraph-duration",
+ "funcgraph-irqs",
+ "funcgraph-overhead",
+ "funcgraph-overrun",
+ "funcgraph-proc",
+ "funcgraph-tail",
+ "func_stack_trace",
+ "function-fork",
+ "function-trace",
+ "graph-time",
+ "hex",
+ "irq-info",
+ "latency-format",
+ "markers",
+ "overwrite",
+ "pause-on-trace",
+ "printk-msg-only",
+ "print-parent",
+ "raw",
+ "record-cmd",
+ "record-tgid",
+ "sleep-time",
+ "stacktrace",
+ "sym-addr",
+ "sym-offset",
+ "sym-userobj",
+ "trace_printk",
+ "userstacktrace",
+ "verbose" };
+
+static int trace_on_off(int fd, bool on)
+{
+ const char *val = on ? "1" : "0";
+ int ret;
+
+ ret = write(fd, val, 1);
+ if (ret == 1)
+ return 0;
+
+ return -1;
+}
+
+static int trace_on_off_file(struct tracefs_instance *instance, bool on)
+{
+ int ret;
+ int fd;
+
+ fd = tracefs_instance_file_open(instance, TRACE_CTRL, O_WRONLY);
+ if (fd < 0)
+ return -1;
+ ret = trace_on_off(fd, on);
+ close(fd);
+
+ return ret;
+}
+
+/**
+ * tracefs_trace_is_on - Check if writing traces to the ring buffer is enabled
+ * @instance: ftrace instance, can be NULL for the top instance
+ *
+ * Returns -1 in case of an error, 0 if tracing is disable or 1 if tracing
+ * is enabled.
+ */
+int tracefs_trace_is_on(struct tracefs_instance *instance)
+{
+ long long res;
+
+ if (tracefs_instance_file_read_number(instance, TRACE_CTRL, &res) == 0)
+ return (int)res;
+
+ return -1;
+}
+
+/**
+ * tracefs_trace_on - Enable writing traces to the ring buffer of the given instance
+ * @instance: ftrace instance, can be NULL for the top instance
+ *
+ * Returns -1 in case of an error or 0 otherwise
+ */
+int tracefs_trace_on(struct tracefs_instance *instance)
+{
+ return trace_on_off_file(instance, true);
+}
+
+/**
+ * tracefs_trace_off - Disable writing traces to the ring buffer of the given instance
+ * @instance: ftrace instance, can be NULL for the top instance
+ *
+ * Returns -1 in case of an error or 0 otherwise
+ */
+int tracefs_trace_off(struct tracefs_instance *instance)
+{
+ return trace_on_off_file(instance, false);
+}
+
+/**
+ * tracefs_trace_on_fd - Enable writing traces to the ring buffer
+ * @fd: File descriptor to ftrace tracing_on file, previously opened
+ * with tracefs_trace_on_get_fd()
+ *
+ * Returns -1 in case of an error or 0 otherwise
+ */
+int tracefs_trace_on_fd(int fd)
+{
+ if (fd < 0)
+ return -1;
+ return trace_on_off(fd, true);
+}
+
+/**
+ * tracefs_trace_off_fd - Disable writing traces to the ring buffer
+ * @fd: File descriptor to ftrace tracing_on file, previously opened
+ * with tracefs_trace_on_get_fd()
+ *
+ * Returns -1 in case of an error or 0 otherwise
+ */
+int tracefs_trace_off_fd(int fd)
+{
+ if (fd < 0)
+ return -1;
+ return trace_on_off(fd, false);
+}
+
+/**
+ * tracefs_option_name - Get trace option name from id
+ * @id: trace option id
+ *
+ * Returns string with option name, or "unknown" in case of not known option id.
+ * The returned string must *not* be freed.
+ */
+const char *tracefs_option_name(enum tracefs_option_id id)
+{
+ /* Make sure options map contains all the options */
+ BUILD_BUG_ON(ARRAY_SIZE(options_map) != TRACEFS_OPTION_MAX);
+
+ if (id < TRACEFS_OPTION_MAX)
+ return options_map[id];
+
+ return options_map[0];
+}
+
+/**
+ * tracefs_option_id - Get trace option ID from name
+ * @name: trace option name
+ *
+ * Returns trace option ID or TRACEFS_OPTION_INVALID in case of an error or
+ * unknown option name.
+ */
+enum tracefs_option_id tracefs_option_id(const char *name)
+{
+ int i;
+
+ if (!name)
+ return TRACEFS_OPTION_INVALID;
+
+ for (i = 0; i < TRACEFS_OPTION_MAX; i++) {
+ if (strlen(name) == strlen(options_map[i]) &&
+ !strcmp(options_map[i], name))
+ return i;
+ }
+
+ return TRACEFS_OPTION_INVALID;
+}
+
+const static struct tracefs_options_mask *
+trace_get_options(struct tracefs_instance *instance, bool enabled)
+{
+ pthread_mutex_t *lock = trace_get_lock(instance);
+ struct tracefs_options_mask *bitmask;
+ enum tracefs_option_id id;
+ unsigned long long set;
+ char file[PATH_MAX];
+ struct stat st;
+ long long val;
+ char *path;
+ int ret;
+
+ bitmask = enabled ? enabled_opts_mask(instance) :
+ supported_opts_mask(instance);
+
+ for (id = 1; id < TRACEFS_OPTION_MAX; id++) {
+ snprintf(file, PATH_MAX, "options/%s", options_map[id]);
+ path = tracefs_instance_get_file(instance, file);
+ if (!path)
+ return NULL;
+
+ set = 1;
+ ret = stat(path, &st);
+ if (ret < 0 || !S_ISREG(st.st_mode)) {
+ set = 0;
+ } else if (enabled) {
+ ret = tracefs_instance_file_read_number(instance, file, &val);
+ if (ret != 0 || val != 1)
+ set = 0;
+ }
+
+ pthread_mutex_lock(lock);
+ bitmask->mask = (bitmask->mask & ~(1ULL << (id - 1))) | (set << (id - 1));
+ pthread_mutex_unlock(lock);
+
+ tracefs_put_tracing_file(path);
+ }
+
+
+ return bitmask;
+}
+
+/**
+ * tracefs_options_get_supported - Get all supported trace options in given instance
+ * @instance: ftrace instance, can be NULL for the top instance
+ *
+ * Returns bitmask structure with all trace options, supported in given instance,
+ * or NULL in case of an error.
+ */
+const struct tracefs_options_mask *
+tracefs_options_get_supported(struct tracefs_instance *instance)
+{
+ return trace_get_options(instance, false);
+}
+
+/**
+ * tracefs_options_get_enabled - Get all currently enabled trace options in given instance
+ * @instance: ftrace instance, can be NULL for the top instance
+ *
+ * Returns bitmask structure with all trace options, enabled in given instance,
+ * or NULL in case of an error.
+ */
+const struct tracefs_options_mask *
+tracefs_options_get_enabled(struct tracefs_instance *instance)
+{
+ return trace_get_options(instance, true);
+}
+
+static int trace_config_option(struct tracefs_instance *instance,
+ enum tracefs_option_id id, bool set)
+{
+ const char *set_str = set ? "1" : "0";
+ char file[PATH_MAX];
+ const char *name;
+
+ name = tracefs_option_name(id);
+ if (!name)
+ return -1;
+
+ snprintf(file, PATH_MAX, "options/%s", name);
+ if (strlen(set_str) != tracefs_instance_file_write(instance, file, set_str))
+ return -1;
+ return 0;
+}
+
+/**
+ * tracefs_option_enable - Enable trace option
+ * @instance: ftrace instance, can be NULL for the top instance
+ * @id: trace option id
+ *
+ * Returns -1 in case of an error or 0 otherwise
+ */
+int tracefs_option_enable(struct tracefs_instance *instance, enum tracefs_option_id id)
+{
+ return trace_config_option(instance, id, true);
+}
+
+/**
+ * tracefs_option_disable - Disable trace option
+ * @instance: ftrace instance, can be NULL for the top instance
+ * @id: trace option id
+ *
+ * Returns -1 in case of an error or 0 otherwise
+ */
+int tracefs_option_disable(struct tracefs_instance *instance, enum tracefs_option_id id)
+{
+ return trace_config_option(instance, id, false);
+}
+
+/**
+ * tracefs_option_is_supported - Check if an option is supported
+ * @instance: ftrace instance, can be NULL for the top instance
+ * @id: trace option id
+ *
+ * Returns true if an option with given id is supported by the system, false if
+ * it is not supported.
+ */
+bool tracefs_option_is_supported(struct tracefs_instance *instance, enum tracefs_option_id id)
+{
+ const char *name = tracefs_option_name(id);
+ char file[PATH_MAX];
+
+ if (!name)
+ return false;
+ snprintf(file, PATH_MAX, "options/%s", name);
+ return tracefs_file_exists(instance, file);
+}
+
+/**
+ * tracefs_option_is_enabled - Check if an option is enabled in given instance
+ * @instance: ftrace instance, can be NULL for the top instance
+ * @id: trace option id
+ *
+ * Returns true if an option with given id is enabled in the given instance,
+ * false if it is not enabled.
+ */
+bool tracefs_option_is_enabled(struct tracefs_instance *instance, enum tracefs_option_id id)
+{
+ const char *name = tracefs_option_name(id);
+ char file[PATH_MAX];
+ long long res;
+
+ if (!name)
+ return false;
+ snprintf(file, PATH_MAX, "options/%s", name);
+ if (!tracefs_instance_file_read_number(instance, file, &res) && res)
+ return true;
+
+ return false;
+}
+
+/**
+ * tracefs_option_mask_is_set - Check if given option is set in the bitmask
+ * @options: Options bitmask
+ * @id: trace option id
+ *
+ * Returns true if an option with given id is set in the bitmask,
+ * false if it is not set.
+ */
+bool tracefs_option_mask_is_set(const struct tracefs_options_mask *options,
+ enum tracefs_option_id id)
+{
+ if (id > TRACEFS_OPTION_INVALID)
+ return options->mask & (1ULL << (id - 1));
+ return false;
+}
+
+struct func_list {
+ struct func_list *next;
+ char *func;
+ unsigned int start;
+ unsigned int end;
+};
+
+struct func_filter {
+ const char *filter;
+ regex_t re;
+ bool set;
+ bool is_regex;
+};
+
+static bool is_regex(const char *str)
+{
+ int i;
+
+ for (i = 0; str[i]; i++) {
+ switch (str[i]) {
+ case 'a' ... 'z':
+ case 'A'...'Z':
+ case '_':
+ case '0'...'9':
+ case '*':
+ case '.':
+ /* Dots can be part of a function name */
+ case '?':
+ continue;
+ default:
+ return true;
+ }
+ }
+ return false;
+}
+
+static char *update_regex(const char *reg)
+{
+ int len = strlen(reg);
+ char *str;
+
+ if (reg[0] == '^' && reg[len - 1] == '$')
+ return strdup(reg);
+
+ str = malloc(len + 3);
+ if (reg[0] == '^') {
+ strcpy(str, reg);
+ } else {
+ str[0] = '^';
+ strcpy(str + 1, reg);
+ len++; /* add ^ */
+ }
+ if (str[len - 1] != '$')
+ str[len++]= '$';
+ str[len] = '\0';
+ return str;
+}
+
+/*
+ * Convert a glob into a regular expression.
+ */
+static char *make_regex(const char *glob)
+{
+ char *str;
+ int cnt = 0;
+ int i, j;
+
+ for (i = 0; glob[i]; i++) {
+ if (glob[i] == '*'|| glob[i] == '.')
+ cnt++;
+ }
+
+ /* '^' + ('*'->'.*' or '.' -> '\.') + '$' + '\0' */
+ str = malloc(i + cnt + 3);
+ if (!str)
+ return NULL;
+
+ str[0] = '^';
+ for (i = 0, j = 1; glob[i]; i++, j++) {
+ if (glob[i] == '*')
+ str[j++] = '.';
+ /* Dots can be part of a function name */
+ if (glob[i] == '.')
+ str[j++] = '\\';
+ str[j] = glob[i];
+ }
+ str[j++] = '$';
+ str[j] = '\0';
+ return str;
+}
+
+static bool match(const char *str, struct func_filter *func_filter)
+{
+ return regexec(&func_filter->re, str, 0, NULL, 0) == 0;
+}
+
+/*
+ * Return 0 on success, -1 error writing, 1 on other errors.
+ */
+static int write_filter(int fd, const char *filter, const char *module)
+{
+ char *each_str = NULL;
+ int write_size;
+ int size;
+
+ if (module)
+ write_size = asprintf(&each_str, "%s:mod:%s ", filter, module);
+ else
+ write_size = asprintf(&each_str, "%s ", filter);
+
+ if (write_size < 0)
+ return 1;
+
+ size = write(fd, each_str, write_size);
+ free(each_str);
+
+ /* compare written bytes*/
+ if (size < write_size)
+ return -1;
+
+ return 0;
+}
+
+static int add_func(struct func_list ***next_func_ptr, unsigned int index)
+{
+ struct func_list **next_func = *next_func_ptr;
+ struct func_list *func_list = *next_func;
+
+ if (!func_list) {
+ func_list = calloc(1, sizeof(*func_list));
+ if (!func_list)
+ return -1;
+ func_list->start = index;
+ func_list->end = index;
+ *next_func = func_list;
+ return 0;
+ }
+
+ if (index == func_list->end + 1) {
+ func_list->end = index;
+ return 0;
+ }
+ *next_func_ptr = &func_list->next;
+ return add_func(next_func_ptr, index);
+}
+
+static int add_func_str(struct func_list ***next_func_ptr, const char *func)
+{
+ struct func_list **next_func = *next_func_ptr;
+ struct func_list *func_list = *next_func;
+
+ if (!func_list) {
+ func_list = calloc(1, sizeof(*func_list));
+ if (!func_list)
+ return -1;
+ func_list->func = strdup(func);
+ if (!func_list->func)
+ return -1;
+ *next_func = func_list;
+ return 0;
+ }
+ *next_func_ptr = &func_list->next;
+ return add_func_str(next_func_ptr, func);
+}
+
+static void free_func_list(struct func_list *func_list)
+{
+ struct func_list *f;
+
+ while (func_list) {
+ f = func_list;
+ func_list = f->next;
+ free(f->func);
+ free(f);
+ }
+}
+
+enum match_type {
+ FILTER_CHECK = (1 << 0),
+ FILTER_WRITE = (1 << 1),
+ FILTER_FUTURE = (1 << 2),
+ SAVE_STRING = (1 << 2),
+};
+
+static int match_filters(int fd, struct func_filter *func_filter,
+ const char *module, struct func_list **func_list,
+ int flags)
+{
+ enum match_type type = flags & (FILTER_CHECK | FILTER_WRITE);
+ bool save_str = flags & SAVE_STRING;
+ bool future = flags & FILTER_FUTURE;
+ bool mod_match = false;
+ char *line = NULL;
+ size_t size = 0;
+ char *path;
+ FILE *fp;
+ int index = 0;
+ int ret = 1;
+ int mlen;
+
+ path = tracefs_get_tracing_file(TRACE_FILTER_LIST);
+ if (!path)
+ return 1;
+
+ fp = fopen(path, "r");
+ tracefs_put_tracing_file(path);
+
+ if (!fp)
+ return 1;
+
+ if (module)
+ mlen = strlen(module);
+
+ while (getline(&line, &size, fp) >= 0) {
+ char *saveptr = NULL;
+ char *tok, *mtok;
+ int len = strlen(line);
+
+ if (line[len - 1] == '\n')
+ line[len - 1] = '\0';
+ tok = strtok_r(line, " ", &saveptr);
+ if (!tok)
+ goto next;
+
+ index++;
+
+ if (module) {
+ mtok = strtok_r(NULL, " ", &saveptr);
+ if (!mtok)
+ goto next;
+ if ((strncmp(mtok + 1, module, mlen) != 0) ||
+ (mtok[mlen + 1] != ']'))
+ goto next;
+ if (future)
+ mod_match = true;
+ }
+ switch (type) {
+ case FILTER_CHECK:
+ if (match(tok, func_filter)) {
+ func_filter->set = true;
+ if (save_str)
+ ret = add_func_str(&func_list, tok);
+ else
+ ret = add_func(&func_list, index);
+ if (ret)
+ goto out;
+ }
+ break;
+ case FILTER_WRITE:
+ /* Writes only have one filter */
+ if (match(tok, func_filter)) {
+ ret = write_filter(fd, tok, module);
+ if (ret)
+ goto out;
+ }
+ break;
+ default:
+ /* Should never happen */
+ ret = -1;
+ goto out;
+
+ }
+ next:
+ free(line);
+ line = NULL;
+ len = 0;
+ }
+ out:
+ free(line);
+ fclose(fp);
+
+ /* If there was no matches and future was set, this is a success */
+ if (future && !mod_match)
+ ret = 0;
+
+ return ret;
+}
+
+static int check_available_filters(struct func_filter *func_filter,
+ const char *module,
+ struct func_list **func_list,
+ bool future)
+{
+ int flags = FILTER_CHECK | (future ? FILTER_FUTURE : 0);
+
+ return match_filters(-1, func_filter, module, func_list, flags);
+}
+
+
+static int list_available_filters(struct func_filter *func_filter,
+ const char *module,
+ struct func_list **func_list)
+{
+ int flags = FILTER_CHECK | SAVE_STRING;
+
+ return match_filters(-1, func_filter, module, func_list, flags);
+}
+
+static int set_regex_filter(int fd, struct func_filter *func_filter,
+ const char *module)
+{
+ return match_filters(fd, func_filter, module, NULL, FILTER_WRITE);
+}
+
+static int controlled_write(int fd, struct func_filter *func_filter,
+ const char *module)
+{
+ const char *filter = func_filter->filter;
+ int ret;
+
+ if (func_filter->is_regex)
+ ret = set_regex_filter(fd, func_filter, module);
+ else
+ ret = write_filter(fd, filter, module);
+
+ return ret;
+}
+
+static int init_func_filter(struct func_filter *func_filter, const char *filter)
+{
+ char *str;
+ int ret;
+
+ if (!(func_filter->is_regex = is_regex(filter)))
+ str = make_regex(filter);
+ else
+ str = update_regex(filter);
+
+ if (!str)
+ return -1;
+
+ ret = regcomp(&func_filter->re, str, REG_ICASE|REG_NOSUB);
+ free(str);
+
+ if (ret < 0)
+ return -1;
+
+ func_filter->filter = filter;
+ return 0;
+}
+
+static int write_number(int fd, unsigned int start, unsigned int end)
+{
+ char buf[64];
+ unsigned int i;
+ int n, ret;
+
+ for (i = start; i <= end; i++) {
+ n = snprintf(buf, 64, "%d ", i);
+ ret = write(fd, buf, n);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+/*
+ * This will try to write the first number, if that fails, it
+ * will assume that it is not supported and return 1.
+ * If the first write succeeds, but a following write fails, then
+ * the kernel does support this, but something else went wrong,
+ * in this case, return -1.
+ */
+static int write_func_list(int fd, struct func_list *list)
+{
+ int ret;
+
+ if (!list)
+ return 0;
+
+ ret = write_number(fd, list->start, list->end);
+ if (ret)
+ return 1; // try a different way
+ list = list->next;
+ while (list) {
+ ret = write_number(fd, list->start, list->end);
+ if (ret)
+ return -1;
+ list = list->next;
+ }
+ return 0;
+}
+
+static int update_filter(const char *filter_path, int *fd,
+ struct tracefs_instance *instance, const char *filter,
+ const char *module, unsigned int flags)
+{
+ struct func_filter func_filter;
+ struct func_list *func_list = NULL;
+ bool reset = flags & TRACEFS_FL_RESET;
+ bool cont = flags & TRACEFS_FL_CONTINUE;
+ bool future = flags & TRACEFS_FL_FUTURE;
+ pthread_mutex_t *lock = trace_get_lock(instance);
+ int open_flags;
+ int ret = 1;
+
+ /* future flag is only applicable to modules */
+ if (future && !module) {
+ errno = EINVAL;
+ return 1;
+ }
+
+ pthread_mutex_lock(lock);
+
+ /* RESET is only allowed if the file is not opened yet */
+ if (reset && *fd >= 0) {
+ errno = EBUSY;
+ ret = -1;
+ goto out;
+ }
+
+ /*
+ * Set EINVAL on no matching filter. But errno may still be modified
+ * on another type of failure (allocation or opening a file).
+ */
+ errno = EINVAL;
+
+ /* module set with NULL filter means to enable all functions in a module */
+ if (module && !filter)
+ filter = "*";
+
+ if (!filter) {
+ /* OK to call without filters if this is closing the opened file */
+ if (!cont && *fd >= 0) {
+ errno = 0;
+ ret = 0;
+ close(*fd);
+ *fd = -1;
+ }
+ /* Also OK to call if reset flag is set */
+ if (reset)
+ goto open_file;
+
+ goto out;
+ }
+
+ if (init_func_filter(&func_filter, filter) < 0)
+ goto out;
+
+ ret = check_available_filters(&func_filter, module, &func_list, future);
+ if (ret)
+ goto out_free;
+
+ open_file:
+ ret = 1;
+
+ open_flags = reset ? O_TRUNC : O_APPEND;
+
+ if (*fd < 0)
+ *fd = open(filter_path, O_WRONLY | O_CLOEXEC | open_flags);
+ if (*fd < 0)
+ goto out_free;
+
+ errno = 0;
+ ret = 0;
+
+ if (filter) {
+ /*
+ * If future is set, and no functions were found, then
+ * set it directly.
+ */
+ if (func_list)
+ ret = write_func_list(*fd, func_list);
+ else
+ ret = 1;
+ if (ret > 0)
+ ret = controlled_write(*fd, &func_filter, module);
+ }
+
+ if (!cont) {
+ close(*fd);
+ *fd = -1;
+ }
+
+ out_free:
+ if (filter)
+ regfree(&func_filter.re);
+ free_func_list(func_list);
+ out:
+ pthread_mutex_unlock(lock);
+
+ return ret;
+}
+
+/**
+ * tracefs_function_filter - filter the functions that are traced
+ * @instance: ftrace instance, can be NULL for top tracing instance.
+ * @filter: The filter to filter what functions are to be traced
+ * @module: Module to be traced or NULL if all functions are to be examined.
+ * @flags: flags on modifying the filter file
+ *
+ * @filter may be a full function name, a glob, or a regex. It will be
+ * considered a regex, if there's any characters that are not normally in
+ * function names or "*" or "?" for a glob.
+ *
+ * @flags:
+ * TRACEFS_FL_RESET - will clear the functions in the filter file
+ * before applying the @filter. This will error with -1
+ * and errno of EBUSY if this flag is set and a previous
+ * call had the same instance and TRACEFS_FL_CONTINUE set.
+ * TRACEFS_FL_CONTINUE - will keep the filter file open on return.
+ * The filter is updated on closing of the filter file.
+ * With this flag set, the file is not closed, and more filters
+ * may be added before they take effect. The last call of this
+ * function must be called without this flag for the filter
+ * to take effect.
+ * TRACEFS_FL_FUTURE - only applicable if "module" is set. If no match
+ * is made, and the module is not yet loaded, it will still attempt
+ * to write the filter plus the module; "<filter>:mod:<module>"
+ * to the filter file. Starting with Linux kernels 4.13, it is possible
+ * to load the filter file with module functions for a module that
+ * is not yet loaded, and when the module is loaded, it will then
+ * activate the module.
+ *
+ * Returns 0 on success, 1 if there was an error but the filtering has not
+ * yet started, -1 if there was an error but the filtering has started.
+ * If -1 is returned and TRACEFS_FL_CONTINUE was set, then this function
+ * needs to be called again without the TRACEFS_FL_CONTINUE flag to commit
+ * the changes and close the filter file.
+ */
+int tracefs_function_filter(struct tracefs_instance *instance, const char *filter,
+ const char *module, unsigned int flags)
+{
+ char *filter_path;
+ int *fd;
+ int ret;
+
+ filter_path = tracefs_instance_get_file(instance, TRACE_FILTER);
+ if (!filter_path)
+ return -1;
+
+ if (instance)
+ fd = &instance->ftrace_filter_fd;
+ else
+ fd = &ftrace_filter_fd;
+
+ ret = update_filter(filter_path, fd, instance, filter, module, flags);
+ tracefs_put_tracing_file(filter_path);
+ return ret;
+}
+
+/**
+ * tracefs_function_notrace - filter the functions that are not to be traced
+ * @instance: ftrace instance, can be NULL for top tracing instance.
+ * @filter: The filter to filter what functions are not to be traced
+ * @module: Module to be traced or NULL if all functions are to be examined.
+ * @flags: flags on modifying the filter file
+ *
+ * See tracefs_function_filter, as this has the same functionality but
+ * for adding to the "notrace" filter.
+ */
+int tracefs_function_notrace(struct tracefs_instance *instance, const char *filter,
+ const char *module, unsigned int flags)
+{
+ char *filter_path;
+ int *fd;
+ int ret;
+
+ filter_path = tracefs_instance_get_file(instance, TRACE_NOTRACE);
+ if (!filter_path)
+ return -1;
+
+ if (instance)
+ fd = &instance->ftrace_notrace_fd;
+ else
+ fd = &ftrace_notrace_fd;
+
+ ret = update_filter(filter_path, fd, instance, filter, module, flags);
+ tracefs_put_tracing_file(filter_path);
+ return ret;
+}
+
+int write_tracer(int fd, const char *tracer)
+{
+ int ret;
+
+ ret = write(fd, tracer, strlen(tracer));
+ if (ret < strlen(tracer))
+ return -1;
+ return ret;
+}
+
+/**
+ * tracefs_set_tracer - function to set the tracer
+ * @instance: ftrace instance, can be NULL for top tracing instance
+ * @tracer: The tracer enum that defines the tracer to be set
+ * @t: A tracer name if TRACEFS_TRACER_CUSTOM is passed in for @tracer
+ *
+ * Set the tracer for the instance based on the tracefs_tracer enums.
+ * If the user wishes to enable a tracer that is not defined by
+ * the enum (new or custom kernel), the tracer can be set to
+ * TRACEFS_TRACER_CUSTOM, and pass in a const char * name for
+ * the tracer to set.
+ *
+ * Returns 0 on succes, negative on error.
+ */
+
+int tracefs_tracer_set(struct tracefs_instance *instance,
+ enum tracefs_tracers tracer, ...)
+{
+ char *tracer_path = NULL;
+ const char *t = NULL;
+ int ret = -1;
+ int fd = -1;
+ int i;
+
+ if (tracer < 0 || tracer > ARRAY_SIZE(tracers)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ tracer_path = tracefs_instance_get_file(instance, CUR_TRACER);
+ if (!tracer_path)
+ return -1;
+
+ fd = open(tracer_path, O_WRONLY);
+ if (fd < 0) {
+ errno = ENOENT;
+ goto out;
+ }
+
+ if (tracer == TRACEFS_TRACER_CUSTOM) {
+ va_list ap;
+
+ va_start(ap, tracer);
+ t = va_arg(ap, const char *);
+ va_end(ap);
+ } else if (tracer == tracer_enums[tracer]) {
+ t = tracers[tracer];
+ } else {
+ for (i = 0; i < ARRAY_SIZE(tracer_enums); i++) {
+ if (tracer == tracer_enums[i]) {
+ t = tracers[i];
+ break;
+ }
+ }
+ }
+ if (!t) {
+ errno = EINVAL;
+ goto out;
+ }
+ ret = write_tracer(fd, t);
+ /*
+ * If the tracer does not exist, EINVAL is returned,
+ * but let the user know this as ENODEV.
+ */
+ if (ret < 0 && errno == EINVAL)
+ errno = ENODEV;
+ out:
+ tracefs_put_tracing_file(tracer_path);
+ close(fd);
+ return ret > 0 ? 0 : ret;
+}
+
+int tracefs_tracer_clear(struct tracefs_instance *instance)
+{
+ return tracefs_tracer_set(instance, TRACEFS_TRACER_NOP);
+}
+
+static bool splice_safe(int fd, int pfd)
+{
+ int ret;
+
+ errno = 0;
+ ret = splice(pfd, NULL, fd, NULL,
+ 10, SPLICE_F_NONBLOCK | SPLICE_F_MOVE);
+
+ return !ret || (ret < 0 && errno == EAGAIN);
+}
+
+static ssize_t read_trace_pipe(bool *keep_going, int in_fd, int out_fd)
+{
+ char buf[BUFSIZ];
+ ssize_t bread = 0;
+ int ret;
+
+ while (*(volatile bool *)keep_going) {
+ int r;
+ ret = read(in_fd, buf, BUFSIZ);
+ if (ret <= 0)
+ break;
+ r = ret;
+ ret = write(out_fd, buf, r);
+ if (ret < 0)
+ break;
+ bread += ret;
+ /*
+ * If the write does a partial write, then
+ * the iteration should stop. This can happen if
+ * the destination file system ran out of disk space.
+ * Sure, it probably lost a little from the read
+ * but there's not much more that can be
+ * done. Just return what was transferred.
+ */
+ if (ret < r)
+ break;
+ }
+
+ if (ret < 0 && (errno == EAGAIN || errno == EINTR))
+ ret = 0;
+
+ return ret < 0 ? ret : bread;
+}
+
+static bool top_pipe_keep_going;
+
+/**
+ * tracefs_trace_pipe_stream - redirect the stream of trace data to an output
+ * file. The "splice" system call is used to moves the data without copying
+ * between kernel address space and user address space. The user can interrupt
+ * the streaming of the data by pressing Ctrl-c.
+ * @fd: The file descriptor of the output file.
+ * @instance: ftrace instance, can be NULL for top tracing instance.
+ * @flags: flags for opening the trace_pipe file.
+ *
+ * Returns -1 in case of an error or number of bytes transferred otherwise.
+ */
+ssize_t tracefs_trace_pipe_stream(int fd, struct tracefs_instance *instance,
+ int flags)
+{
+ bool *keep_going = instance ? &instance->pipe_keep_going :
+ &top_pipe_keep_going;
+ const char *file = "trace_pipe";
+ int brass[2], in_fd, ret = -1;
+ int sflags = flags & O_NONBLOCK ? SPLICE_F_NONBLOCK : 0;
+ off_t data_size;
+ ssize_t bread = 0;
+
+ (*(volatile bool *)keep_going) = true;
+
+ in_fd = tracefs_instance_file_open(instance, file, O_RDONLY | flags);
+ if (in_fd < 0) {
+ tracefs_warning("Failed to open 'trace_pipe'.");
+ return ret;
+ }
+
+ if(pipe(brass) < 0) {
+ tracefs_warning("Failed to open pipe.");
+ goto close_file;
+ }
+
+ data_size = fcntl(brass[0], F_GETPIPE_SZ);
+ if (data_size <= 0) {
+ tracefs_warning("Failed to open pipe (size=0).");
+ goto close_all;
+ }
+
+ /* Test if the output is splice safe */
+ if (!splice_safe(fd, brass[0])) {
+ bread = read_trace_pipe(keep_going, in_fd, fd);
+ ret = 0; /* Force return of bread */
+ goto close_all;
+ }
+
+ errno = 0;
+
+ while (*(volatile bool *)keep_going) {
+ ret = splice(in_fd, NULL,
+ brass[1], NULL,
+ data_size, sflags);
+ if (ret < 0)
+ break;
+
+ ret = splice(brass[0], NULL,
+ fd, NULL,
+ data_size, sflags);
+ if (ret < 0)
+ break;
+ bread += ret;
+ }
+
+ /*
+ * Do not return error in the case when the "splice" system call
+ * was interrupted by the user (pressing Ctrl-c).
+ * Or if NONBLOCK was specified.
+ */
+ if (!keep_going || errno == EAGAIN || errno == EINTR)
+ ret = 0;
+
+ close_all:
+ close(brass[0]);
+ close(brass[1]);
+ close_file:
+ close(in_fd);
+
+ return ret ? ret : bread;
+}
+
+/**
+ * tracefs_trace_pipe_print - redirect the stream of trace data to "stdout".
+ * The "splice" system call is used to moves the data without copying
+ * between kernel address space and user address space.
+ * @instance: ftrace instance, can be NULL for top tracing instance.
+ * @flags: flags for opening the trace_pipe file.
+ *
+ * Returns -1 in case of an error or number of bytes transferred otherwise.
+ */
+
+ssize_t tracefs_trace_pipe_print(struct tracefs_instance *instance, int flags)
+{
+ return tracefs_trace_pipe_stream(STDOUT_FILENO, instance, flags);
+}
+
+/**
+ * tracefs_trace_pipe_stop - stop the streaming of trace data.
+ * @instance: ftrace instance, can be NULL for top tracing instance.
+ */
+void tracefs_trace_pipe_stop(struct tracefs_instance *instance)
+{
+ if (instance)
+ instance->pipe_keep_going = false;
+ else
+ top_pipe_keep_going = false;
+}
+
+/**
+ * tracefs_filter_functions - return a list of available functons that can be filtered
+ * @filter: The filter to filter what functions to list (can be NULL for all)
+ * @module: Module to be traced or NULL if all functions are to be examined.
+ * @list: The list to return the list from (freed by tracefs_list_free() on success)
+ *
+ * Returns a list of function names that match @filter and @module. If both
+ * @filter and @module is NULL, then all available functions that can be filtered
+ * will be returned. (Note, there can be duplicates, if there are more than
+ * one function with the same name.
+ *
+ * On success, zero is returned, and @list contains a list of functions that were
+ * found, and must be freed with tracefs_list_free().
+ * On failure, a negative number is returned, and @list is ignored.
+ */
+int tracefs_filter_functions(const char *filter, const char *module, char ***list)
+{
+ struct func_filter func_filter;
+ struct func_list *func_list = NULL, *f;
+ char **funcs = NULL;
+ int ret;
+
+ if (!filter)
+ filter = ".*";
+
+ ret = init_func_filter(&func_filter, filter);
+ if (ret < 0)
+ return ret;
+
+ ret = list_available_filters(&func_filter, module, &func_list);
+ if (ret < 0)
+ goto out;
+
+ ret = -1;
+ for (f = func_list; f; f = f->next) {
+ char **tmp;
+
+ tmp = tracefs_list_add(funcs, f->func);
+ if (!tmp) {
+ tracefs_list_free(funcs);
+ goto out;
+ }
+ funcs = tmp;
+ }
+
+ *list = funcs;
+ ret = 0;
+out:
+ regfree(&func_filter.re);
+ free_func_list(func_list);
+ return ret;
+}