summaryrefslogtreecommitdiffstats
path: root/src/tracefs-utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/tracefs-utils.c')
-rw-r--r--src/tracefs-utils.c624
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;
+}