diff options
Diffstat (limited to 'src/tracefs-utils.c')
-rw-r--r-- | src/tracefs-utils.c | 624 |
1 files changed, 624 insertions, 0 deletions
diff --git a/src/tracefs-utils.c b/src/tracefs-utils.c new file mode 100644 index 0000000..9acf2ad --- /dev/null +++ b/src/tracefs-utils.c @@ -0,0 +1,624 @@ +// 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 <stdlib.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <linux/limits.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +#include <event-parse.h> +#include <event-utils.h> +#include "tracefs.h" +#include "tracefs-local.h" + +#define TRACEFS_PATH "/sys/kernel/tracing" +#define DEBUGFS_PATH "/sys/kernel/debug" + +#define ERROR_LOG "error_log" + +#define _STR(x) #x +#define STR(x) _STR(x) + +static int log_level = TEP_LOG_CRITICAL; +static char *custom_tracing_dir; + +/** + * tracefs_set_loglevel - set log level of the library + * @level: desired level of the library messages + */ +void tracefs_set_loglevel(enum tep_loglevel level) +{ + log_level = level; + tep_set_loglevel(level); +} + +void __weak tracefs_warning(const char *fmt, ...) +{ + va_list ap; + + if (log_level < TEP_LOG_WARNING) + return; + + va_start(ap, fmt); + tep_vprint("libtracefs", TEP_LOG_WARNING, true, fmt, ap); + va_end(ap); +} + +static int mount_tracefs(void) +{ + struct stat st; + int ret; + + /* make sure debugfs exists */ + ret = stat(TRACEFS_PATH, &st); + if (ret < 0) + return -1; + + ret = mount("nodev", TRACEFS_PATH, + "tracefs", 0, NULL); + + return ret; +} + +static int mount_debugfs(void) +{ + struct stat st; + int ret; + + /* make sure debugfs exists */ + ret = stat(DEBUGFS_PATH, &st); + if (ret < 0) + return -1; + + ret = mount("nodev", DEBUGFS_PATH, + "debugfs", 0, NULL); + + return ret; +} + +/* Exported for testing purpose only */ +__hidden char *find_tracing_dir(bool debugfs, bool mount) +{ + char *debug_str = NULL; + char fspath[PATH_MAX+1]; + char *tracing_dir; + char type[100]; + int use_debug = 0; + FILE *fp; + + fp = fopen("/proc/mounts", "r"); + if (!fp) { + tracefs_warning("Can't open /proc/mounts for read"); + return NULL; + } + + while (fscanf(fp, "%*s %" + STR(PATH_MAX) + "s %99s %*s %*d %*d\n", + fspath, type) == 2) { + if (!debugfs && strcmp(type, "tracefs") == 0) + break; + if (!debug_str && strcmp(type, "debugfs") == 0) { + if (debugfs) + break; + debug_str = strdup(fspath); + if (!debug_str) { + fclose(fp); + return NULL; + } + } + } + fclose(fp); + + if (debugfs) { + if (strcmp(type, "debugfs") != 0) { + if (!mount || mount_debugfs() < 0) + return NULL; + strcpy(fspath, DEBUGFS_PATH); + } + } else if (strcmp(type, "tracefs") != 0) { + if (!mount || mount_tracefs() < 0) { + if (debug_str) { + strncpy(fspath, debug_str, PATH_MAX); + fspath[PATH_MAX] = 0; + } else { + if (!mount || mount_debugfs() < 0) { + if (mount) + tracefs_warning("debugfs not mounted, please mount"); + free(debug_str); + return NULL; + } + strcpy(fspath, DEBUGFS_PATH); + } + use_debug = 1; + } else + strcpy(fspath, TRACEFS_PATH); + } + free(debug_str); + + if (use_debug) { + int ret; + + ret = asprintf(&tracing_dir, "%s/tracing", fspath); + if (ret < 0) + return NULL; + } else { + tracing_dir = strdup(fspath); + if (!tracing_dir) + return NULL; + } + + return tracing_dir; +} + +/** + * tracefs_tracing_dir_is_mounted - test if the tracing dir is already mounted + * @mount: Mount it if it is not already mounted + * @path: the path to the tracing directory if mounted or was mounted + * + * Returns 1 if the tracing directory is already mounted and 0 if it is not. + * If @mount is set and it fails to mount, it returns -1. + * + * If path is not NULL, and the tracing directory is or was mounted, it holds + * the path to the tracing directory. It must not be freed. + */ +int tracefs_tracing_dir_is_mounted(bool mount, const char **path) +{ + const char *dir; + + dir = find_tracing_dir(false, false); + if (dir) { + if (path) + *path = dir; + return 1; + } + if (!mount) + return 0; + + dir = find_tracing_dir(false, mount); + if (!dir) + return -1; + if (path) + *path = dir; + return 0; +} + +/** + * trace_find_tracing_dir - Find tracing directory + * @debugfs: Boolean to just return the debugfs directory + * + * Returns string containing the full path to the system's tracing directory. + * The string must be freed by free() + */ +__hidden char *trace_find_tracing_dir(bool debugfs) +{ + return find_tracing_dir(debugfs, false); +} + +/** + * tracefs_set_tracing_dir - Set location of the tracing directory + * @tracing_dir: full path to the system's tracing directory mount point. + * + * Set the location to the system's tracing directory. This API should be used + * to set a custom location of the tracing directory. There is no need to call + * it if the location is standard, in that case the library will auto detect it. + * + * Returns 0 on success, -1 otherwise. + */ +int tracefs_set_tracing_dir(char *tracing_dir) +{ + if (custom_tracing_dir) { + free(custom_tracing_dir); + custom_tracing_dir = NULL; + } + + if (tracing_dir) { + custom_tracing_dir = strdup(tracing_dir); + if (!custom_tracing_dir) + return -1; + } + + return 0; +} + +/* Used to check if the directory is still mounted */ +static int test_dir(const char *dir, const char *file) +{ + char path[strlen(dir) + strlen(file) + 2]; + struct stat st; + + sprintf(path, "%s/%s", dir, file); + return stat(path, &st) < 0 ? 0 : 1; +} + +/** + * tracefs_tracing_dir - Get tracing directory + * + * Returns string containing the full path to the system's tracing directory. + * The returned string must *not* be freed. + */ +const char *tracefs_tracing_dir(void) +{ + static const char *tracing_dir; + + /* Do not check custom_tracing_dir */ + if (custom_tracing_dir) + return custom_tracing_dir; + + if (tracing_dir && test_dir(tracing_dir, "trace")) + return tracing_dir; + + tracing_dir = find_tracing_dir(false, true); + return tracing_dir; +} + +/** + * tracefs_debug_dir - Get debugfs directory path + * + * Returns string containing the full path to the system's debugfs directory. + * + * The returned string must *not* be freed. + */ +const char *tracefs_debug_dir(void) +{ + static const char *debug_dir; + + if (debug_dir && test_dir(debug_dir, "tracing")) + return debug_dir; + + debug_dir = find_tracing_dir(true, true); + return debug_dir; +} + +/** + * tracefs_get_tracing_file - Get tracing file + * @name: tracing file name + * + * Returns string containing the full path to a tracing file in + * the system's tracing directory. + * + * Must use tracefs_put_tracing_file() to free the returned string. + */ +char *tracefs_get_tracing_file(const char *name) +{ + const char *tracing; + char *file; + int ret; + + if (!name) + return NULL; + + tracing = tracefs_tracing_dir(); + if (!tracing) + return NULL; + + ret = asprintf(&file, "%s/%s", tracing, name); + if (ret < 0) + return NULL; + + return file; +} + +/** + * tracefs_put_tracing_file - Free tracing file or directory name + * + * Frees tracing file or directory, returned by + * tracefs_get_tracing_file()API. + */ +void tracefs_put_tracing_file(char *name) +{ + free(name); +} + +__hidden int str_read_file(const char *file, char **buffer, bool warn) +{ + char stbuf[BUFSIZ]; + char *buf = NULL; + int size = 0; + char *nbuf; + int fd; + int r; + + fd = open(file, O_RDONLY); + if (fd < 0) { + if (warn) + tracefs_warning("File %s not found", file); + return -1; + } + + do { + r = read(fd, stbuf, BUFSIZ); + if (r <= 0) + continue; + nbuf = realloc(buf, size+r+1); + if (!nbuf) { + if (warn) + tracefs_warning("Failed to allocate file buffer"); + size = -1; + break; + } + buf = nbuf; + memcpy(buf+size, stbuf, r); + size += r; + } while (r > 0); + + close(fd); + if (r == 0 && size > 0) { + buf[size] = '\0'; + *buffer = buf; + } else + free(buf); + + return size; +} + +/** + * tracefs_error_all - return the content of the error log + * @instance: The instance to read the error log from (NULL for top level) + * + * Return NULL if the log is empty, or on error (where errno will be + * set. Otherwise the content of the entire log is returned in a string + * that must be freed with free(). + */ +char *tracefs_error_all(struct tracefs_instance *instance) +{ + char *content; + char *path; + int size; + + errno = 0; + + path = tracefs_instance_get_file(instance, ERROR_LOG); + if (!path) + return NULL; + size = str_read_file(path, &content, false); + tracefs_put_tracing_file(path); + + if (size <= 0) + return NULL; + + return content; +} + +enum line_states { + START, + CARROT, +}; + +/** + * tracefs_error_last - return the last error logged + * @instance: The instance to read the error log from (NULL for top level) + * + * Return NULL if the log is empty, or on error (where errno will be + * set. Otherwise a string containing the content of the last error shown +* in the log that must be freed with free(). + */ +char *tracefs_error_last(struct tracefs_instance *instance) +{ + enum line_states state = START; + char *content; + char *ret; + bool done = false; + int size; + int i; + + content = tracefs_error_all(instance); + if (!content) + return NULL; + + size = strlen(content); + if (!size) /* Should never happen */ + return content; + + for (i = size - 1; i > 0; i--) { + switch (state) { + case START: + if (content[i] == '\n') { + /* Remove extra new lines */ + content[i] = '\0'; + break; + } + if (content[i] == '^') + state = CARROT; + break; + case CARROT: + if (content[i] == '\n') { + /* Remember last new line */ + size = i; + break; + } + if (content[i] == '^') { + /* Go just passed the last newline */ + i = size + 1; + done = true; + } + break; + } + if (done) + break; + } + + if (i) { + ret = strdup(content + i); + free(content); + } else { + ret = content; + } + + return ret; +} + +/** + * tracefs_error_clear - clear the error log of an instance + * @instance: The instance to clear (NULL for top level) + * + * Clear the content of the error log. + * + * Returns 0 on success, -1 otherwise. + */ +int tracefs_error_clear(struct tracefs_instance *instance) +{ + return tracefs_instance_file_clear(instance, ERROR_LOG); +} + +/** + * tracefs_list_free - free list if strings, returned by APIs + * tracefs_event_systems() + * tracefs_system_events() + * + *@list pointer to a list of strings, the last one must be NULL + */ +void tracefs_list_free(char **list) +{ + int i; + + if (!list) + return; + + for (i = 0; list[i]; i++) + free(list[i]); + + /* The allocated list is before the user visible portion */ + list--; + free(list); +} + + +__hidden char ** trace_list_create_empty(void) +{ + char **list; + + list = calloc(2, sizeof(*list)); + + return list ? &list[1] : NULL; +} + +/** + * tracefs_list_add - create or extend a string list + * @list: The list to add to (NULL to create a new one) + * @string: The string to append to @list. + * + * If @list is NULL, a new list is created with the first element + * a copy of @string, and the second element is NULL. + * + * If @list is not NULL, it is then reallocated to include + * a new element and a NULL terminator, and will return the new + * allocated array on success, and the one passed in should be + * ignored. + * + * Returns an allocated string array that must be freed with + * tracefs_list_free() on success. On failure, NULL is returned + * and the @list is untouched. + */ +char **tracefs_list_add(char **list, const char *string) +{ + unsigned long size = 0; + char *str = strdup(string); + char **new_list; + + if (!str) + return NULL; + + /* + * The returned list is really the address of the + * second entry of the list (&list[1]), the first + * entry contains the number of strings in the list. + */ + if (list) { + list--; + size = *(unsigned long *)list; + } + + new_list = realloc(list, sizeof(*list) * (size + 3)); + if (!new_list) { + free(str); + return NULL; + } + + list = new_list; + list[0] = (char *)(size + 1); + list++; + list[size++] = str; + list[size] = NULL; + + return list; +} + +/* + * trace_list_pop - Removes the last string added + * @list: The list to remove the last event from + * + * Returns 0 on success, -1 on error. + * Returns 1 if the list is empty or NULL. + */ +__hidden int trace_list_pop(char **list) +{ + unsigned long size; + + if (!list || list[0]) + return 1; + + list--; + size = *(unsigned long *)list; + /* size must be greater than zero */ + if (!size) + return -1; + size--; + *list = (char *)size; + list++; + list[size] = NULL; + return 0; +} + +/** + * tracefs_list_size - Return the number of strings in the list + * @list: The list to determine the size. + * + * Returns the number of elements in the list. + * If @list is NULL, then zero is returned. + */ +int tracefs_list_size(char **list) +{ + if (!list) + return 0; + + list--; + return (int)*(unsigned long *)list; +} + +/** + * tracefs_tracer_available - test if a tracer is available + * @tracing_dir: The directory that contains the tracing directory + * @tracer: The name of the tracer + * + * Return true if the tracer is available + */ +bool tracefs_tracer_available(const char *tracing_dir, const char *tracer) +{ + bool ret = false; + char **tracers = NULL; + int i; + + tracers = tracefs_tracers(tracing_dir); + if (!tracers) + return false; + + for (i = 0; tracers[i]; i++) { + if (strcmp(tracer, tracers[i]) == 0) { + ret = true; + break; + } + } + + tracefs_list_free(tracers); + return ret; +} |