aboutsummaryrefslogtreecommitdiff
path: root/src/tracefs-instance.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/tracefs-instance.c')
-rw-r--r--src/tracefs-instance.c1241
1 files changed, 1241 insertions, 0 deletions
diff --git a/src/tracefs-instance.c b/src/tracefs-instance.c
new file mode 100644
index 0000000..57f5c7f
--- /dev/null
+++ b/src/tracefs-instance.c
@@ -0,0 +1,1241 @@
+// 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->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_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;
+}
+
+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_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_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;
+}