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