| // 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 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; |
| |
| free(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); |
| } |
| |
| /* The function is copied from trace-cmd */ |
| __hidden char *strstrip(char *str) |
| { |
| char *s; |
| |
| if (!str) |
| return NULL; |
| |
| s = str + strlen(str) - 1; |
| while (s >= str && isspace(*s)) |
| s--; |
| s++; |
| *s = '\0'; |
| |
| for (s = str; *s && isspace(*s); s++) |
| ; |
| |
| return s; |
| } |
| |
| __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; |
| } |
| |
| /** |
| * tracefs_instance_get_buffer_percent - get the instance buffer percent |
| * @instance: The instance to get from (NULL for toplevel) |
| * |
| * Returns the buffer percent setting of the given instance. |
| * (-1 if not found). |
| */ |
| int tracefs_instance_get_buffer_percent(struct tracefs_instance *instance) |
| { |
| long long val; |
| int ret; |
| |
| ret = tracefs_instance_file_read_number(instance, "buffer_percent", &val); |
| return !ret ? (int)val : ret; |
| } |
| |
| /** |
| * tracefs_instance_set_buffer_percent - set the instance buffer percent |
| * @instance: The instance to set (NULL for toplevel) |
| * |
| * Returns zero on success or -1 on error |
| */ |
| int tracefs_instance_set_buffer_percent(struct tracefs_instance *instance, int val) |
| { |
| return tracefs_instance_file_write_number(instance, "buffer_percent", val); |
| } |