diff options
Diffstat (limited to 'src/tracefs-tools.c')
-rw-r--r-- | src/tracefs-tools.c | 1273 |
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; +} |