| // SPDX-License-Identifier: LGPL-2.1 |
| /* |
| * Copyright (C) 2008, 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> |
| * |
| * Updates: |
| * Copyright (C) 2019, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com> |
| * |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <dirent.h> |
| #include <regex.h> |
| #include <limits.h> |
| #include <pthread.h> |
| #include "tracefs.h" |
| #include "tracefs-local.h" |
| |
| enum { |
| FLAG_INSTANCE_NEWLY_CREATED = (1 << 0), |
| FLAG_INSTANCE_DELETED = (1 << 1), |
| }; |
| |
| |
| struct tracefs_options_mask toplevel_supported_opts; |
| struct tracefs_options_mask toplevel_enabled_opts; |
| |
| __hidden inline struct tracefs_options_mask * |
| supported_opts_mask(struct tracefs_instance *instance) |
| { |
| return instance ? &instance->supported_opts : &toplevel_supported_opts; |
| } |
| |
| __hidden inline struct tracefs_options_mask * |
| enabled_opts_mask(struct tracefs_instance *instance) |
| { |
| return instance ? &instance->enabled_opts : &toplevel_enabled_opts; |
| } |
| |
| /** |
| * instance_alloc - allocate a new ftrace instance |
| * @trace_dir - Full path to the tracing directory, where the instance is |
| * @name: The name of the instance (instance will point to this) |
| * |
| * Returns a newly allocated instance, or NULL in case of an error. |
| */ |
| static struct tracefs_instance *instance_alloc(const char *trace_dir, const char *name) |
| { |
| struct tracefs_instance *instance; |
| |
| instance = calloc(1, sizeof(*instance)); |
| if (!instance) |
| goto error; |
| instance->trace_dir = strdup(trace_dir); |
| if (!instance->trace_dir) |
| goto error; |
| if (name) { |
| instance->name = strdup(name); |
| if (!instance->name) |
| goto error; |
| } |
| |
| if (pthread_mutex_init(&instance->lock, NULL) < 0) |
| goto error; |
| |
| instance->ftrace_filter_fd = -1; |
| instance->ftrace_notrace_fd = -1; |
| instance->ftrace_marker_fd = -1; |
| instance->ftrace_marker_raw_fd = -1; |
| |
| return instance; |
| |
| error: |
| if (instance) { |
| free(instance->name); |
| free(instance->trace_dir); |
| free(instance); |
| } |
| return NULL; |
| } |
| |
| |
| __hidden int trace_get_instance(struct tracefs_instance *instance) |
| { |
| int ret; |
| |
| pthread_mutex_lock(&instance->lock); |
| if (instance->flags & FLAG_INSTANCE_DELETED) { |
| ret = -1; |
| } else { |
| instance->ref++; |
| ret = 0; |
| } |
| pthread_mutex_unlock(&instance->lock); |
| return ret; |
| } |
| |
| __hidden void trace_put_instance(struct tracefs_instance *instance) |
| { |
| pthread_mutex_lock(&instance->lock); |
| if (--instance->ref < 0) |
| instance->flags |= FLAG_INSTANCE_DELETED; |
| pthread_mutex_unlock(&instance->lock); |
| |
| if (!(instance->flags & FLAG_INSTANCE_DELETED)) |
| return; |
| |
| if (instance->ftrace_filter_fd >= 0) |
| close(instance->ftrace_filter_fd); |
| |
| if (instance->ftrace_notrace_fd >= 0) |
| close(instance->ftrace_notrace_fd); |
| |
| if (instance->ftrace_marker_fd >= 0) |
| close(instance->ftrace_marker_fd); |
| |
| if (instance->ftrace_marker_raw_fd >= 0) |
| close(instance->ftrace_marker_raw_fd); |
| |
| free(instance->trace_dir); |
| free(instance->followers); |
| free(instance->missed_followers); |
| free(instance->name); |
| pthread_mutex_destroy(&instance->lock); |
| free(instance); |
| } |
| |
| /** |
| * tracefs_instance_free - Free an instance, previously allocated by |
| tracefs_instance_create() |
| * @instance: Pointer to the instance to be freed |
| * |
| */ |
| void tracefs_instance_free(struct tracefs_instance *instance) |
| { |
| if (!instance) |
| return; |
| |
| trace_put_instance(instance); |
| } |
| |
| static mode_t get_trace_file_permissions(char *name) |
| { |
| mode_t rmode = 0; |
| struct stat st; |
| char *path; |
| int ret; |
| |
| path = tracefs_get_tracing_file(name); |
| if (!path) |
| return 0; |
| ret = stat(path, &st); |
| if (ret) |
| goto out; |
| rmode = st.st_mode & ACCESSPERMS; |
| out: |
| tracefs_put_tracing_file(path); |
| return rmode; |
| } |
| |
| /** |
| * tracefs_instance_is_new - Check if the instance is newly created by the library |
| * @instance: Pointer to an ftrace instance |
| * |
| * Returns true, if the ftrace instance is newly created by the library or |
| * false otherwise. |
| */ |
| bool tracefs_instance_is_new(struct tracefs_instance *instance) |
| { |
| if (instance && (instance->flags & FLAG_INSTANCE_NEWLY_CREATED)) |
| return true; |
| return false; |
| } |
| |
| /** |
| * tracefs_instance_create - Create a new ftrace instance |
| * @name: Name of the instance to be created |
| * |
| * Allocates and initializes a new instance structure. If the instance does not |
| * exist in the system, create it. |
| * Returns a pointer to a newly allocated instance, or NULL in case of an error. |
| * The returned instance must be freed by tracefs_instance_free(). |
| */ |
| struct tracefs_instance *tracefs_instance_create(const char *name) |
| { |
| struct tracefs_instance *inst = NULL; |
| char *path = NULL; |
| const char *tdir; |
| struct stat st; |
| mode_t mode; |
| int ret; |
| |
| tdir = tracefs_tracing_dir(); |
| if (!tdir) |
| return NULL; |
| inst = instance_alloc(tdir, name); |
| if (!inst) |
| return NULL; |
| |
| path = tracefs_instance_get_dir(inst); |
| ret = stat(path, &st); |
| if (ret < 0) { |
| /* Cannot create the top instance, if it does not exist! */ |
| if (!name) |
| goto error; |
| mode = get_trace_file_permissions("instances"); |
| if (mkdir(path, mode)) |
| goto error; |
| inst->flags |= FLAG_INSTANCE_NEWLY_CREATED; |
| } |
| tracefs_put_tracing_file(path); |
| return inst; |
| |
| error: |
| tracefs_put_tracing_file(path); |
| tracefs_instance_free(inst); |
| return NULL; |
| } |
| |
| /** |
| * tracefs_instance_alloc - Allocate an instance structure for existing trace instance |
| * @tracing_dir: full path to the system trace directory, where the new instance is |
| * if NULL, the default top tracing directory is used. |
| * @name: Name of the instance. |
| * |
| * Allocates and initializes a new instance structure. If the instance does not |
| * exist, do not create it and exit with error. |
| * Returns a pointer to a newly allocated instance, or NULL in case of an error |
| * or the requested instance does not exists. |
| * The returned instance must be freed by tracefs_instance_free(). |
| */ |
| struct tracefs_instance *tracefs_instance_alloc(const char *tracing_dir, |
| const char *name) |
| { |
| struct tracefs_instance *inst = NULL; |
| char file[PATH_MAX]; |
| const char *tdir; |
| struct stat st; |
| int ret; |
| |
| if (tracing_dir) { |
| ret = stat(tracing_dir, &st); |
| if (ret < 0 || !S_ISDIR(st.st_mode)) |
| return NULL; |
| tdir = tracing_dir; |
| |
| } else |
| tdir = tracefs_tracing_dir(); |
| if (!tdir) |
| return NULL; |
| |
| if (name) { |
| sprintf(file, "%s/instances/%s", tdir, name); |
| ret = stat(file, &st); |
| if (ret < 0 || !S_ISDIR(st.st_mode)) |
| return NULL; |
| } |
| inst = instance_alloc(tdir, name); |
| |
| return inst; |
| } |
| |
| /** |
| * tracefs_instance_destroy - Remove a ftrace instance |
| * @instance: Pointer to the instance to be removed |
| * |
| * Returns -1 in case of an error, or 0 otherwise. |
| */ |
| int tracefs_instance_destroy(struct tracefs_instance *instance) |
| { |
| char *path; |
| int ret = -1; |
| |
| if (!instance || !instance->name) { |
| tracefs_warning("Cannot remove top instance"); |
| return -1; |
| } |
| |
| path = tracefs_instance_get_dir(instance); |
| if (path) |
| ret = rmdir(path); |
| tracefs_put_tracing_file(path); |
| if (ret) { |
| pthread_mutex_lock(&instance->lock); |
| instance->flags |= FLAG_INSTANCE_DELETED; |
| pthread_mutex_unlock(&instance->lock); |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * tracefs_instance_get_file - return the path to an instance file. |
| * @instance: ftrace instance, can be NULL for the top instance |
| * @file: name of file to return |
| * |
| * Returns the path of the @file for the given @instance, or NULL in |
| * case of an error. |
| * |
| * Must use tracefs_put_tracing_file() to free the returned string. |
| */ |
| char * |
| tracefs_instance_get_file(struct tracefs_instance *instance, const char *file) |
| { |
| char *path = NULL; |
| int ret; |
| |
| if (!instance) |
| return tracefs_get_tracing_file(file); |
| if (!instance->name) |
| ret = asprintf(&path, "%s/%s", instance->trace_dir, file); |
| else |
| ret = asprintf(&path, "%s/instances/%s/%s", |
| instance->trace_dir, instance->name, file); |
| if (ret < 0) |
| return NULL; |
| |
| return path; |
| } |
| |
| /** |
| * tracefs_instance_get_dir - return the path to the instance directory. |
| * @instance: ftrace instance, can be NULL for the top instance |
| * |
| * Returns the full path to the instance directory |
| * |
| * Must use tracefs_put_tracing_file() to free the returned string. |
| */ |
| char *tracefs_instance_get_dir(struct tracefs_instance *instance) |
| { |
| char *path = NULL; |
| int ret; |
| |
| if (!instance) /* Top instance of default system trace directory */ |
| return trace_find_tracing_dir(false); |
| |
| if (!instance->name) |
| return strdup(instance->trace_dir); |
| |
| ret = asprintf(&path, "%s/instances/%s", instance->trace_dir, instance->name); |
| if (ret < 0) { |
| tracefs_warning("Failed to allocate path for instance %s", |
| instance->name); |
| return NULL; |
| } |
| |
| return path; |
| } |
| |
| /** |
| * tracefs_instance_get_name - return the name of an instance |
| * @instance: ftrace instance |
| * |
| * Returns the name of the given @instance. |
| * The returned string must *not* be freed. |
| */ |
| const char *tracefs_instance_get_name(struct tracefs_instance *instance) |
| { |
| if (instance) |
| return instance->name; |
| return NULL; |
| } |
| |
| /** |
| * tracefs_instance_get_buffer_size - return the buffer size of the ring buffer |
| * @instance: The instance to get the buffer size from |
| * @cpu: if less that zero, will return the total size, otherwise the cpu size |
| * |
| * Returns the buffer size. If @cpu is less than zero, it returns the total size |
| * of the ring buffer otherwise it returs the size of the buffer for the given |
| * CPU. |
| * |
| * Returns -1 on error. |
| */ |
| ssize_t tracefs_instance_get_buffer_size(struct tracefs_instance *instance, int cpu) |
| { |
| unsigned long long size; |
| char *path; |
| char *val; |
| int ret; |
| |
| if (cpu < 0) { |
| val = tracefs_instance_file_read(instance, "buffer_total_size_kb", NULL); |
| } else { |
| ret = asprintf(&path, "per_cpu/cpu%d/buffer_size_kb", cpu); |
| if (ret < 0) |
| return ret; |
| |
| val = tracefs_instance_file_read(instance, path, NULL); |
| free(path); |
| } |
| |
| if (!val) |
| return -1; |
| |
| size = strtoull(val, NULL, 0); |
| free(val); |
| return size; |
| } |
| |
| /** |
| * tracefs_instance_set_buffer_size - modify the ring buffer size |
| * @instance: The instance to modify (NULL for the top level) |
| * @size: The size in kilobytes to to set the size to |
| * @cpu: the CPU to set it to (-1 for all CPUs) |
| * |
| * Sets the size of the ring buffer per CPU buffers. If @cpu is negative, |
| * then it sets the ring buffer size for all the per CPU buffers, otherwise |
| * it only sets the per CPU buffer specified by @cpu. |
| * |
| * Returns 0 on success and -1 on error. |
| */ |
| int tracefs_instance_set_buffer_size(struct tracefs_instance *instance, size_t size, int cpu) |
| { |
| char *path; |
| char *val; |
| int ret; |
| |
| ret = asprintf(&val, "%zd", size); |
| if (ret < 0) |
| return ret; |
| |
| if (cpu < 0) { |
| ret = tracefs_instance_file_write(instance, "buffer_size_kb", val); |
| } else { |
| ret = asprintf(&path, "per_cpu/cpu%d/buffer_size_kb", cpu); |
| if (ret < 0) { |
| free(val); |
| return ret; |
| } |
| |
| ret = tracefs_instance_file_write(instance, path, val); |
| free(path); |
| } |
| free(val); |
| |
| return ret < 0 ? -1 : 0; |
| } |
| |
| /** |
| * tracefs_instance_get_subbuf_size - return the sub-buffer size of the ring buffer |
| * @instance: The instance to get the buffer size from |
| * |
| * Returns the sub-buffer size in kilobytes. |
| * Returns -1 on error. |
| */ |
| ssize_t tracefs_instance_get_subbuf_size(struct tracefs_instance *instance) |
| { |
| long long size; |
| int ret; |
| |
| ret = tracefs_instance_file_read_number(instance, "buffer_subbuf_size_kb", &size); |
| if (ret < 0) |
| return ret; |
| |
| return size; |
| } |
| |
| /** |
| * tracefs_instance_set_buffer_size - modify the ring buffer sub-buffer size |
| * @instance: The instance to modify (NULL for the top level) |
| * @size: The size in kilobytes to to set the sub-buffer size to |
| * |
| * Sets the sub-buffer size in kilobytes for the given ring buffer. |
| * |
| * Returns 0 on success and -1 on error. |
| */ |
| int tracefs_instance_set_subbuf_size(struct tracefs_instance *instance, size_t size) |
| { |
| int ret; |
| |
| ret = tracefs_instance_file_write_number(instance, "buffer_subbuf_size_kb", size); |
| |
| return ret < 0 ? -1 : 0; |
| } |
| |
| /** |
| * tracefs_instance_get_trace_dir - return the top trace directory, where the instance is confuigred |
| * @instance: ftrace instance |
| * |
| * Returns the top trace directory where the given @instance is configured. |
| * The returned string must *not* be freed. |
| */ |
| const char *tracefs_instance_get_trace_dir(struct tracefs_instance *instance) |
| { |
| if (instance) |
| return instance->trace_dir; |
| return NULL; |
| } |
| |
| static int write_file(const char *file, const char *str, int flags) |
| { |
| int ret = 0; |
| int fd; |
| |
| fd = open(file, flags); |
| if (fd < 0) { |
| tracefs_warning("Failed to open '%s'", file); |
| return -1; |
| } |
| |
| if (str) |
| ret = write(fd, str, strlen(str)); |
| |
| close(fd); |
| return ret; |
| } |
| |
| static int instance_file_write(struct tracefs_instance *instance, |
| const char *file, const char *str, int flags) |
| { |
| struct stat st; |
| char *path; |
| int ret; |
| |
| path = tracefs_instance_get_file(instance, file); |
| if (!path) |
| return -1; |
| ret = stat(path, &st); |
| if (ret == 0) |
| ret = write_file(path, str, flags); |
| tracefs_put_tracing_file(path); |
| |
| return ret; |
| } |
| |
| /** |
| * tracefs_instance_file_write - Write in trace file of specific instance. |
| * @instance: ftrace instance, can be NULL for the top instance |
| * @file: name of the file |
| * @str: nul terminated string, that will be written in the file. |
| * |
| * Returns the number of written bytes, or -1 in case of an error |
| */ |
| int tracefs_instance_file_write(struct tracefs_instance *instance, |
| const char *file, const char *str) |
| { |
| return instance_file_write(instance, file, str, O_WRONLY | O_TRUNC); |
| } |
| |
| /** |
| * tracefs_instance_file_write_number - Write integer from a trace file. |
| * @instance: ftrace instance, can be NULL for the top instance |
| * @file: name of the file |
| * @res: The integer to write to @file |
| * |
| * Returns 0 if the write succeeds, -1 on error. |
| */ |
| int tracefs_instance_file_write_number(struct tracefs_instance *instance, |
| const char *file, size_t val) |
| { |
| char buf[64]; |
| int ret; |
| |
| snprintf(buf, 64, "%zd\n", val); |
| |
| ret = tracefs_instance_file_write(instance, file, buf); |
| |
| return ret > 1 ? 0 : -1; |
| } |
| |
| /** |
| * tracefs_instance_file_append - Append to a trace file of specific instance. |
| * @instance: ftrace instance, can be NULL for the top instance. |
| * @file: name of the file. |
| * @str: nul terminated string, that will be appended to the file. |
| * |
| * Returns the number of appended bytes, or -1 in case of an error. |
| */ |
| int tracefs_instance_file_append(struct tracefs_instance *instance, |
| const char *file, const char *str) |
| { |
| return instance_file_write(instance, file, str, O_WRONLY); |
| } |
| |
| /** |
| * tracefs_instance_file_clear - Clear a trace file of specific instance. |
| * Note, it only opens with O_TRUNC and closes the file. If the file has |
| * content that does not get cleared in this way, this will not have any |
| * effect. For example, set_ftrace_filter can have probes that are not |
| * cleared by O_TRUNC: |
| * |
| * echo "schedule:stacktrace" > set_ftrace_filter |
| * |
| * This function will not clear the above "set_ftrace_filter" after that |
| * command. |
| * @instance: ftrace instance, can be NULL for the top instance. |
| * @file: name of the file to clear. |
| * |
| * Returns 0 on success, or -1 in case of an error. |
| */ |
| int tracefs_instance_file_clear(struct tracefs_instance *instance, |
| const char *file) |
| { |
| return instance_file_write(instance, file, NULL, O_WRONLY | O_TRUNC); |
| } |
| |
| /** |
| * tracefs_instance_file_read - Read from a trace file of specific instance. |
| * @instance: ftrace instance, can be NULL for the top instance |
| * @file: name of the file |
| * @psize: returns the number of bytes read |
| * |
| * Returns a pointer to a nul terminated string, read from the file, or NULL in |
| * case of an error. |
| * The return string must be freed by free() |
| */ |
| char *tracefs_instance_file_read(struct tracefs_instance *instance, |
| const char *file, int *psize) |
| { |
| char *buf = NULL; |
| int size = 0; |
| char *path; |
| |
| path = tracefs_instance_get_file(instance, file); |
| if (!path) |
| return NULL; |
| |
| size = str_read_file(path, &buf, true); |
| |
| tracefs_put_tracing_file(path); |
| if (buf && psize) |
| *psize = size; |
| |
| return buf; |
| } |
| |
| /** |
| * tracefs_instance_file_read_number - Read long long integer from a trace file. |
| * @instance: ftrace instance, can be NULL for the top instance |
| * @file: name of the file |
| * @res: The integer from the file. |
| * |
| * Returns 0 if the reading is successful and the result is stored in res, -1 |
| * in case of an error. |
| */ |
| int tracefs_instance_file_read_number(struct tracefs_instance *instance, |
| const char *file, long long *res) |
| { |
| long long num; |
| int ret = -1; |
| int size = 0; |
| char *endptr; |
| char *str; |
| |
| str = tracefs_instance_file_read(instance, file, &size); |
| if (size && str) { |
| errno = 0; |
| num = strtoll(str, &endptr, 0); |
| if (errno == 0 && str != endptr) { |
| *res = num; |
| ret = 0; |
| } |
| } |
| free(str); |
| return ret; |
| } |
| |
| /** |
| * tracefs_instance_file_open - Open a trace file for reading and writing |
| * @instance: ftrace instance, can be NULL for the top instance |
| * @file: name of the file |
| * @mode: file open flags, -1 for default O_RDWR |
| * |
| * Returns -1 in case of an error, or a valid file descriptor otherwise. |
| * The returned FD must be closed with close() |
| */ |
| int tracefs_instance_file_open(struct tracefs_instance *instance, |
| const char *file, int mode) |
| { |
| int flags = O_RDWR; |
| int fd = -1; |
| char *path; |
| |
| path = tracefs_instance_get_file(instance, file); |
| if (!path) |
| return -1; |
| |
| if (mode >= 0) |
| flags = mode; |
| fd = open(path, flags); |
| tracefs_put_tracing_file(path); |
| |
| return fd; |
| } |
| |
| static bool check_file_exists(struct tracefs_instance *instance, |
| const char *name, bool dir) |
| { |
| char file[PATH_MAX]; |
| struct stat st; |
| char *path; |
| int ret; |
| |
| path = tracefs_instance_get_dir(instance); |
| if (name) |
| snprintf(file, PATH_MAX, "%s/%s", path, name); |
| else |
| snprintf(file, PATH_MAX, "%s", path); |
| tracefs_put_tracing_file(path); |
| ret = stat(file, &st); |
| if (ret < 0) |
| return false; |
| |
| return !dir == !S_ISDIR(st.st_mode); |
| } |
| |
| /** |
| * tracefs_instance_exists - Check an instance with given name exists |
| * @name: name of the instance |
| * |
| * Returns true if the instance exists, false otherwise |
| * |
| */ |
| bool tracefs_instance_exists(const char *name) |
| { |
| char file[PATH_MAX]; |
| |
| if (!name) |
| return false; |
| snprintf(file, PATH_MAX, "instances/%s", name); |
| return check_file_exists(NULL, file, true); |
| } |
| |
| /** |
| * tracefs_file_exists - Check if a file with given name exists in given instance |
| * @instance: ftrace instance, can be NULL for the top instance |
| * @name: name of the file |
| * |
| * Returns true if the file exists, false otherwise |
| * |
| * If a directory with the given name exists, false is returned. |
| */ |
| bool tracefs_file_exists(struct tracefs_instance *instance, const char *name) |
| { |
| return check_file_exists(instance, name, false); |
| } |
| |
| /** |
| * tracefs_dir_exists - Check if a directory with given name exists in given instance |
| * @instance: ftrace instance, can be NULL for the top instance |
| * @name: name of the directory |
| * |
| * Returns true if the directory exists, false otherwise |
| */ |
| bool tracefs_dir_exists(struct tracefs_instance *instance, const char *name) |
| { |
| return check_file_exists(instance, name, true); |
| } |
| |
| /** |
| * tracefs_instances_walk - Iterate through all ftrace instances in the system |
| * @callback: user callback, called for each instance. Instance name is passed |
| * as input parameter. If the @callback returns non-zero, |
| * the iteration stops. |
| * @context: user context, passed to the @callback. |
| * |
| * Returns -1 in case of an error, 1 if the iteration was stopped because of the |
| * callback return value or 0 otherwise. |
| */ |
| int tracefs_instances_walk(int (*callback)(const char *, void *), void *context) |
| { |
| struct dirent *dent; |
| char *path = NULL; |
| DIR *dir = NULL; |
| struct stat st; |
| int fret = -1; |
| int ret; |
| |
| path = tracefs_get_tracing_file("instances"); |
| if (!path) |
| return -1; |
| ret = stat(path, &st); |
| if (ret < 0 || !S_ISDIR(st.st_mode)) |
| goto out; |
| |
| dir = opendir(path); |
| if (!dir) |
| goto out; |
| fret = 0; |
| while ((dent = readdir(dir))) { |
| char *instance; |
| |
| if (strcmp(dent->d_name, ".") == 0 || |
| strcmp(dent->d_name, "..") == 0) |
| continue; |
| instance = trace_append_file(path, dent->d_name); |
| ret = stat(instance, &st); |
| free(instance); |
| if (ret < 0 || !S_ISDIR(st.st_mode)) |
| continue; |
| if (callback(dent->d_name, context)) { |
| fret = 1; |
| break; |
| } |
| } |
| |
| out: |
| if (dir) |
| closedir(dir); |
| tracefs_put_tracing_file(path); |
| return fret; |
| } |
| |
| static inline bool match(const char *str, regex_t *re) |
| { |
| if (!re) |
| return true; |
| return regexec(re, str, 0, NULL, 0) == 0; |
| } |
| |
| struct instance_list { |
| regex_t *re; |
| char **list; |
| int failed; |
| }; |
| |
| static int build_list(const char *name, void *data) |
| { |
| struct instance_list *list = data; |
| char **instances; |
| int ret = -1; |
| |
| if (!match(name, list->re)) |
| return 0; |
| |
| instances = tracefs_list_add(list->list, name); |
| if (!instances) |
| goto out; |
| |
| list->list = instances; |
| ret = 0; |
| |
| out: |
| list->failed = ret; |
| return ret; |
| } |
| |
| /** |
| * tracefs_instances - return a list of instance names |
| * @regex: A regex of instances to filter on (NULL to match all) |
| * |
| * Returns a list of names of existing instances, that must be |
| * freed with tracefs_list_free(). Note, if there are no matches |
| * then an empty list will be returned (not NULL). |
| * NULL on error. |
| */ |
| char **tracefs_instances(const char *regex) |
| { |
| struct instance_list list = { .re = NULL, .list = NULL }; |
| regex_t re; |
| int ret; |
| |
| if (regex) { |
| ret = regcomp(&re, regex, REG_ICASE|REG_NOSUB); |
| if (ret < 0) |
| return NULL; |
| list.re = &re; |
| } |
| |
| ret = tracefs_instances_walk(build_list, &list); |
| if (ret < 0 || list.failed) { |
| tracefs_list_free(list.list); |
| list.list = NULL; |
| } else { |
| /* No matches should produce an empty list */ |
| if (!list.list) |
| list.list = trace_list_create_empty(); |
| } |
| return list.list; |
| } |
| |
| /** |
| * tracefs_get_clock - Get the current trace clock |
| * @instance: ftrace instance, can be NULL for the top instance |
| * |
| * Returns the current trace clock of the given instance, or NULL in |
| * case of an error. |
| * The return string must be freed by free() |
| */ |
| char *tracefs_get_clock(struct tracefs_instance *instance) |
| { |
| char *all_clocks = NULL; |
| char *ret = NULL; |
| int bytes = 0; |
| char *clock; |
| char *cont; |
| |
| all_clocks = tracefs_instance_file_read(instance, "trace_clock", &bytes); |
| if (!all_clocks || !bytes) |
| goto out; |
| |
| clock = strstr(all_clocks, "["); |
| if (!clock) |
| goto out; |
| clock++; |
| cont = strstr(clock, "]"); |
| if (!cont) |
| goto out; |
| *cont = '\0'; |
| |
| ret = strdup(clock); |
| out: |
| free(all_clocks); |
| return ret; |
| } |
| |
| /** |
| * tracefs_instance_set_affinity_raw - write a hex bitmask into the affinity |
| * @instance: The instance to set affinity to (NULL for top level) |
| * @mask: String containing the hex value to set the tracing affinity to. |
| * |
| * Sets the tracing affinity CPU mask for @instance. The @mask is the raw |
| * value that is used to write into the tracing system. |
| * |
| * Return 0 on success and -1 on error. |
| */ |
| int tracefs_instance_set_affinity_raw(struct tracefs_instance *instance, |
| const char *mask) |
| { |
| return tracefs_instance_file_write(instance, "tracing_cpumask", mask); |
| } |
| |
| /** |
| * tracefs_instance_set_affinity_set - use a cpu_set to define tracing affinity |
| * @instance: The instance to set affinity to (NULL for top level) |
| * @set: A CPU set that describes the CPU affinity to set tracing to. |
| * @set_size: The size in bytes of @set (use CPU_ALLOC_SIZE() to get this value) |
| * |
| * Sets the tracing affinity CPU mask for @instance. The bits in @set will be |
| * used to set the CPUs to have tracing on. |
| * |
| * If @set is NULL, then all CPUs defined by sysconf(_SC_NPROCESSORS_CONF) |
| * will be set, and @set_size is ignored. |
| * |
| * Return 0 on success and -1 on error. |
| */ |
| int tracefs_instance_set_affinity_set(struct tracefs_instance *instance, |
| cpu_set_t *set, size_t set_size) |
| { |
| struct trace_seq seq; |
| bool free_set = false; |
| bool hit = false; |
| int nr_cpus; |
| int cpu; |
| int ret = -1; |
| int w, n, i; |
| |
| trace_seq_init(&seq); |
| |
| /* NULL set means all CPUs to be set */ |
| if (!set) { |
| nr_cpus = sysconf(_SC_NPROCESSORS_CONF); |
| set = CPU_ALLOC(nr_cpus); |
| if (!set) |
| goto out; |
| set_size = CPU_ALLOC_SIZE(nr_cpus); |
| CPU_ZERO_S(set_size, set); |
| /* Set all CPUS */ |
| for (cpu = 0; cpu < nr_cpus; cpu++) |
| CPU_SET_S(cpu, set_size, set); |
| free_set = true; |
| } |
| /* Convert to a bitmask hex string */ |
| nr_cpus = (set_size + 1) * 8; |
| if (nr_cpus < 1) { |
| /* Must have at least one bit set */ |
| errno = EINVAL; |
| goto out; |
| } |
| /* Start backwards from 32 bits */ |
| for (w = ((nr_cpus + 31) / 32) - 1; w >= 0; w--) { |
| /* Now move one nibble at a time */ |
| for (n = 7; n >= 0; n--) { |
| int nibble = 0; |
| |
| if ((n * 4) + (w * 32) >= nr_cpus) |
| continue; |
| |
| /* One bit at a time */ |
| for (i = 3; i >= 0; i--) { |
| cpu = (w * 32) + (n * 4) + i; |
| if (cpu >= nr_cpus) |
| continue; |
| if (CPU_ISSET_S(cpu, set_size, set)) { |
| nibble |= 1 << i; |
| hit = true; |
| } |
| } |
| if (hit && trace_seq_printf(&seq, "%x", nibble) < 0) |
| goto out; |
| } |
| if (hit && w) |
| if (trace_seq_putc(&seq, ',') < 0) |
| goto out; |
| } |
| if (!hit) { |
| errno = EINVAL; |
| goto out; |
| } |
| trace_seq_terminate(&seq); |
| ret = tracefs_instance_set_affinity_raw(instance, seq.buffer); |
| out: |
| trace_seq_destroy(&seq); |
| if (free_set) |
| CPU_FREE(set); |
| return ret; |
| } |
| |
| /** |
| * tracefs_instance_set_affinity - Set the affinity defined by CPU values. |
| * @instance: The instance to set affinity to (NULL for top level) |
| * @cpu_str: A string of values that define what CPUs to set. |
| * |
| * Sets the tracing affinity CPU mask for @instance. The @cpu_str is a set |
| * of decimal numbers used to state which CPU should be part of the affinity |
| * mask. A range may also be specified via a hyphen. |
| * |
| * For example, "1,4,6-8" |
| * |
| * The numbers do not need to be in order. |
| * |
| * If @cpu_str is NULL, then all CPUs defined by sysconf(_SC_NPROCESSORS_CONF) |
| * will be set. |
| * |
| * Return 0 on success and -1 on error. |
| */ |
| int tracefs_instance_set_affinity(struct tracefs_instance *instance, |
| const char *cpu_str) |
| { |
| cpu_set_t *set = NULL; |
| size_t set_size; |
| char *word; |
| char *cpus; |
| char *del; |
| char *c; |
| int max_cpu = 0; |
| int cpu1, cpu2; |
| int len; |
| int ret = -1; |
| |
| /* NULL cpu_str means to set all CPUs in the mask */ |
| if (!cpu_str) |
| return tracefs_instance_set_affinity_set(instance, NULL, 0); |
| |
| /* First, find out how many CPUs are needed */ |
| cpus = strdup(cpu_str); |
| if (!cpus) |
| return -1; |
| len = strlen(cpus) + 1; |
| for (word = strtok_r(cpus, ",", &del); word; word = strtok_r(NULL, ",", &del)) { |
| cpu1 = atoi(word); |
| if (cpu1 < 0) { |
| errno = EINVAL; |
| goto out; |
| } |
| if (cpu1 > max_cpu) |
| max_cpu = cpu1; |
| cpu2 = -1; |
| if ((c = strchr(word, '-'))) { |
| c++; |
| cpu2 = atoi(c); |
| if (cpu2 < cpu1) { |
| errno = EINVAL; |
| goto out; |
| } |
| if (cpu2 > max_cpu) |
| max_cpu = cpu2; |
| } |
| } |
| /* |
| * Now ideally, cpus should fit cpu_str as it was orginally allocated |
| * by strdup(). But I'm paranoid, and can imagine someone playing tricks |
| * with threads, and changes cpu_str from another thread and messes |
| * with this. At least only copy what we know is allocated. |
| */ |
| strncpy(cpus, cpu_str, len); |
| |
| set = CPU_ALLOC(max_cpu + 1); |
| if (!set) |
| goto out; |
| set_size = CPU_ALLOC_SIZE(max_cpu + 1); |
| CPU_ZERO_S(set_size, set); |
| |
| for (word = strtok_r(cpus, ",", &del); word; word = strtok_r(NULL, ",", &del)) { |
| cpu1 = atoi(word); |
| if (cpu1 < 0 || cpu1 > max_cpu) { |
| /* Someone playing games? */ |
| errno = EACCES; |
| goto out; |
| } |
| cpu2 = cpu1; |
| if ((c = strchr(word, '-'))) { |
| c++; |
| cpu2 = atoi(c); |
| if (cpu2 < cpu1 || cpu2 > max_cpu) { |
| errno = EACCES; |
| goto out; |
| } |
| } |
| for ( ; cpu1 <= cpu2; cpu1++) |
| CPU_SET(cpu1, set); |
| } |
| ret = tracefs_instance_set_affinity_set(instance, set, set_size); |
| out: |
| free(cpus); |
| CPU_FREE(set); |
| return ret; |
| } |
| |
| /** |
| * tracefs_instance_get_affinity_raw - read the affinity instance file |
| * @instance: The instance to get affinity of (NULL for top level) |
| * |
| * Reads the affinity file for @instance (or the top level if @instance |
| * is NULL) and returns it. The returned string must be freed with free(). |
| * |
| * Returns the affinity mask on success, and must be freed with free() |
| * or NULL on error. |
| */ |
| char *tracefs_instance_get_affinity_raw(struct tracefs_instance *instance) |
| { |
| return tracefs_instance_file_read(instance, "tracing_cpumask", NULL); |
| } |
| |
| static inline int update_cpu_set(int cpus, int cpu_set, int cpu, |
| cpu_set_t *set, size_t set_size) |
| { |
| int bit = 1 << cpu; |
| |
| if (!(cpus & bit)) |
| return 0; |
| |
| CPU_SET_S(cpu_set + cpu, set_size, set); |
| |
| /* |
| * It is possible that the passed in set_size is not big enough |
| * to hold the cpu we just set. If that's the case, do not report |
| * it as being set. |
| * |
| * The CPU_ISSET_S() should return false if the CPU given to it |
| * is bigger than the set itself. |
| */ |
| return CPU_ISSET_S(cpu_set + cpu, set_size, set) ? 1 : 0; |
| } |
| |
| /** |
| * tracefs_instance_get_affinity_set - Retrieve the cpuset of an instance affinity |
| * @instance: The instance to get affinity of (NULL for top level) |
| * @set: A CPU set to put the affinity into. |
| * @set_size: The size in bytes of @set (use CPU_ALLOC_SIZE() to get this value) |
| * |
| * Reads the affinity of a given instance and updates the CPU set by the |
| * instance. |
| * |
| * Returns the number of CPUS that are set, or -1 on error. |
| */ |
| int tracefs_instance_get_affinity_set(struct tracefs_instance *instance, |
| cpu_set_t *set, size_t set_size) |
| { |
| char *affinity; |
| int cpu_set; |
| int cpus; |
| int cnt = 0; |
| int ch; |
| int i; |
| |
| if (!set || !set_size) { |
| errno = -EINVAL; |
| return -1; |
| } |
| |
| affinity = tracefs_instance_get_affinity_raw(instance); |
| if (!affinity) |
| return -1; |
| |
| /* |
| * The returned affinity should be a comma delimited |
| * hex string. Work backwards setting the values. |
| */ |
| cpu_set = 0; |
| i = strlen(affinity); |
| for (i--; i >= 0; i--) { |
| ch = affinity[i]; |
| if (isalnum(ch)) { |
| ch = tolower(ch); |
| if (isdigit(ch)) |
| cpus = ch - '0'; |
| else |
| cpus = ch - 'a' + 10; |
| |
| cnt += update_cpu_set(cpus, cpu_set, 0, set, set_size); |
| cnt += update_cpu_set(cpus, cpu_set, 1, set, set_size); |
| cnt += update_cpu_set(cpus, cpu_set, 2, set, set_size); |
| cnt += update_cpu_set(cpus, cpu_set, 3, set, set_size); |
| /* Next nibble */ |
| cpu_set += 4; |
| } |
| } |
| |
| free(affinity); |
| |
| return cnt; |
| } |
| |
| static inline int update_cpu(int cpus, int cpu_set, int cpu, int s, char **set) |
| { |
| char *list; |
| int bit = 1 << cpu; |
| int ret; |
| |
| if (*set == (char *)-1) |
| return s; |
| |
| if (cpus & bit) { |
| /* If the previous CPU is set just return s */ |
| if (s >= 0) |
| return s; |
| /* Otherwise, return this cpu */ |
| return cpu_set + cpu; |
| } |
| |
| /* If the last CPU wasn't set, just return s */ |
| if (s < 0) |
| return s; |
| |
| /* Update the string */ |
| if (s == cpu_set + cpu - 1) { |
| ret = asprintf(&list, "%s%s%d", |
| *set ? *set : "", *set ? "," : "", s); |
| } else { |
| ret = asprintf(&list, "%s%s%d-%d", |
| *set ? *set : "", *set ? "," : "", |
| s, cpu_set + cpu - 1); |
| } |
| free(*set); |
| /* Force *set to be a failure */ |
| if (ret < 0) |
| *set = (char *)-1; |
| else |
| *set = list; |
| return -1; |
| } |
| |
| /** |
| * tracefs_instance_get_affinity - Retrieve a string of CPUs for instance affinity |
| * @instance: The instance to get affinity of (NULL for top level) |
| * |
| * Reads the affinity of a given instance and returns a CPU count of the |
| * instance. For example, if it reads "eb" it will return: |
| * "0-1,3,5-7" |
| * |
| * If no CPUs are set, an empty string is returned "\0", and it too needs |
| * to be freed. |
| * |
| * Returns an allocated string containing the CPU affinity in "human readable" |
| * format which needs to be freed with free(), or NULL on error. |
| */ |
| char *tracefs_instance_get_affinity(struct tracefs_instance *instance) |
| { |
| char *affinity; |
| char *set = NULL; |
| int cpu_set; |
| int cpus; |
| int ch; |
| int s = -1; |
| int i; |
| |
| affinity = tracefs_instance_get_affinity_raw(instance); |
| if (!affinity) |
| return NULL; |
| |
| /* |
| * The returned affinity should be a comma delimited |
| * hex string. Work backwards setting the values. |
| */ |
| cpu_set = 0; |
| i = strlen(affinity); |
| for (i--; i >= 0; i--) { |
| ch = affinity[i]; |
| if (isalnum(ch)) { |
| ch = tolower(ch); |
| if (isdigit(ch)) |
| cpus = ch - '0'; |
| else |
| cpus = ch - 'a' + 10; |
| s = update_cpu(cpus, cpu_set, 0, s, &set); |
| s = update_cpu(cpus, cpu_set, 1, s, &set); |
| s = update_cpu(cpus, cpu_set, 2, s, &set); |
| s = update_cpu(cpus, cpu_set, 3, s, &set); |
| |
| if (set == (char *)-1) { |
| set = NULL; |
| goto out; |
| } |
| /* Next nibble */ |
| cpu_set += 4; |
| } |
| } |
| /* Clean up in case the last CPU is set */ |
| s = update_cpu(0, cpu_set, 0, s, &set); |
| |
| if (!set) |
| set = strdup(""); |
| out: |
| free(affinity); |
| |
| return set; |
| } |
| |
| static int clear_trigger(const char *file) |
| { |
| char trigger[BUFSIZ]; |
| char *save = NULL; |
| char *line; |
| char *buf; |
| int size; |
| int len; |
| int ret; |
| |
| size = str_read_file(file, &buf, true); |
| if (size < 1) |
| return 0; |
| |
| trigger[0] = '!'; |
| |
| for (line = strtok_r(buf, "\n", &save); line; line = strtok_r(NULL, "\n", &save)) { |
| if (line[0] == '#') |
| continue; |
| len = strlen(line); |
| if (len > BUFSIZ - 2) |
| len = BUFSIZ - 2; |
| strncpy(trigger + 1, line, len); |
| trigger[len + 1] = '\0'; |
| /* We don't want any filters or extra on the line */ |
| strtok(trigger, " "); |
| write_file(file, trigger, O_WRONLY); |
| } |
| |
| free(buf); |
| |
| /* |
| * Some triggers have an order in removing them. |
| * They will not be removed if done in the wrong order. |
| */ |
| size = str_read_file(file, &buf, true); |
| if (size < 1) |
| return 0; |
| |
| ret = 0; |
| for (line = strtok(buf, "\n"); line; line = strtok(NULL, "\n")) { |
| if (line[0] == '#') |
| continue; |
| ret = 1; |
| break; |
| } |
| free(buf); |
| return ret; |
| } |
| |
| static void disable_func_stack_trace_instance(struct tracefs_instance *instance) |
| { |
| char *content; |
| char *cond; |
| int size; |
| |
| content = tracefs_instance_file_read(instance, "current_tracer", &size); |
| if (!content) |
| return; |
| cond = strstrip(content); |
| if (memcmp(cond, "function", size - (cond - content)) != 0) |
| goto out; |
| |
| tracefs_option_disable(instance, TRACEFS_OPTION_FUNC_STACKTRACE); |
| out: |
| free(content); |
| } |
| |
| static void reset_cpu_mask(struct tracefs_instance *instance) |
| { |
| int cpus = sysconf(_SC_NPROCESSORS_CONF); |
| int fullwords = (cpus - 1) / 32; |
| int bits = (cpus - 1) % 32 + 1; |
| int len = (fullwords + 1) * 9; |
| char buf[len + 1]; |
| |
| buf[0] = '\0'; |
| sprintf(buf, "%x", (unsigned int)((1ULL << bits) - 1)); |
| while (fullwords-- > 0) |
| strcat(buf, ",ffffffff"); |
| |
| tracefs_instance_file_write(instance, "tracing_cpumask", buf); |
| } |
| |
| static void clear_func_filter(struct tracefs_instance *instance, const char *file) |
| { |
| char filter[BUFSIZ]; |
| char *line; |
| char *buf; |
| char *p; |
| int len; |
| |
| buf = tracefs_instance_file_read(instance, file, NULL); |
| if (!buf) |
| return; |
| |
| /* Now remove filters */ |
| filter[0] = '!'; |
| |
| /* |
| * To delete a filter, we need to write a '!filter' |
| * to the file for each filter. |
| */ |
| for (line = strtok(buf, "\n"); line; line = strtok(NULL, "\n")) { |
| if (line[0] == '#') |
| continue; |
| len = strlen(line); |
| if (len > BUFSIZ - 2) |
| len = BUFSIZ - 2; |
| |
| strncpy(filter + 1, line, len); |
| filter[len + 1] = '\0'; |
| /* |
| * To remove "unlimited" filters, we must remove |
| * the ":unlimited" from what we write. |
| */ |
| p = strstr(filter, ":unlimited"); |
| if (p) { |
| *p = '\0'; |
| len = p - filter; |
| } |
| /* |
| * The write to this file expects white space |
| * at the end :-p |
| */ |
| filter[len] = '\n'; |
| filter[len+1] = '\0'; |
| tracefs_instance_file_append(instance, file, filter); |
| } |
| free(buf); |
| } |
| |
| static void clear_func_filters(struct tracefs_instance *instance) |
| { |
| int i; |
| const char * const files[] = { "set_ftrace_filter", |
| "set_ftrace_notrace", |
| "set_graph_function", |
| "set_graph_notrace", |
| "stack_trace_filter", |
| NULL }; |
| |
| for (i = 0; files[i]; i++) |
| clear_func_filter(instance, files[i]); |
| } |
| |
| /** |
| * tracefs_instance_clear - clear the trace buffer |
| * @instance: The instance to clear the trace for. |
| * |
| * Returns 0 on succes, -1 on error |
| */ |
| int tracefs_instance_clear(struct tracefs_instance *instance) |
| { |
| return tracefs_instance_file_clear(instance, "trace"); |
| } |
| |
| /** |
| * tracefs_instance_reset - Reset a ftrace instance to its default state |
| * @instance - a ftrace instance to be reseted |
| * |
| * The main logic and the helper functions are copied from |
| * trace-cmd/tracecmd/trace-record.c, trace_reset() |
| */ |
| void tracefs_instance_reset(struct tracefs_instance *instance) |
| { |
| int has_trigger = -1; |
| char **systems; |
| struct stat st; |
| char **file_list = NULL; |
| int list_size = 0; |
| char **events; |
| char *file; |
| int i, j; |
| int ret; |
| |
| tracefs_trace_off(instance); |
| disable_func_stack_trace_instance(instance); |
| tracefs_tracer_clear(instance); |
| tracefs_instance_file_write(instance, "events/enable", "0"); |
| tracefs_instance_file_write(instance, "set_ftrace_pid", ""); |
| tracefs_instance_file_write(instance, "max_graph_depth", "0"); |
| tracefs_instance_file_clear(instance, "trace"); |
| |
| systems = tracefs_event_systems(NULL); |
| if (systems) { |
| for (i = 0; systems[i]; i++) { |
| events = tracefs_system_events(NULL, systems[i]); |
| if (!events) |
| continue; |
| for (j = 0; events[j]; j++) { |
| file = tracefs_event_get_file(instance, systems[i], |
| events[j], "filter"); |
| write_file(file, "0", O_WRONLY | O_TRUNC); |
| tracefs_put_tracing_file(file); |
| |
| file = tracefs_event_get_file(instance, systems[i], |
| events[j], "trigger"); |
| if (has_trigger < 0) { |
| /* Check if the kernel is configured with triggers */ |
| if (stat(file, &st) < 0) |
| has_trigger = 0; |
| else |
| has_trigger = 1; |
| } |
| if (has_trigger) { |
| ret = clear_trigger(file); |
| if (ret) { |
| char **list; |
| list = tracefs_list_add(file_list, file); |
| if (list) |
| file_list = list; |
| } |
| } |
| tracefs_put_tracing_file(file); |
| } |
| tracefs_list_free(events); |
| } |
| tracefs_list_free(systems); |
| } |
| |
| while (file_list && list_size != tracefs_list_size(file_list)) { |
| char **list = file_list; |
| |
| list_size = tracefs_list_size(file_list); |
| file_list = NULL; |
| for (i = 0; list[i]; i++) { |
| file = list[i]; |
| ret = clear_trigger(file); |
| if (ret) { |
| char **tlist; |
| tlist = tracefs_list_add(file_list, list[i]); |
| if (tlist) |
| file_list = tlist; |
| } |
| } |
| tracefs_list_free(list); |
| } |
| tracefs_list_free(file_list); |
| |
| tracefs_instance_file_write(instance, "synthetic_events", " "); |
| tracefs_instance_file_write(instance, "error_log", " "); |
| tracefs_instance_file_write(instance, "trace_clock", "local"); |
| tracefs_instance_file_write(instance, "set_event_pid", ""); |
| reset_cpu_mask(instance); |
| clear_func_filters(instance); |
| tracefs_instance_file_write(instance, "tracing_max_latency", "0"); |
| tracefs_trace_on(instance); |
| } |