| // 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; | |
| } |