blob: 57f5c7f9a000b0d9b925f324256f333470c749b9 [file] [log] [blame]
// 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;
}