| // 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) { |
| free(func_list); |
| 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; |
| } |