// SPDX-License-Identifier: LGPL-2.1 /* * Copyright (C) 2008, 2009, 2010 Red Hat Inc, Steven Rostedt * * Updates: * Copyright (C) 2021, VMware, Tzvetomir Stoyanov * */ #include #include #include #include #include #include #include #include #include #include #include #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; ":mod:" * 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; }