// 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 *
tfs_supported_opts_mask(struct tracefs_instance *instance)
{
	return instance ? &instance->supported_opts : &toplevel_supported_opts;
}

__hidden inline struct tracefs_options_mask *
tfs_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 tfs_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 tfs_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->followers);
	free(instance->missed_followers);
	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;

	tfs_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_put_tracing_file(path);
	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 tfs_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;
}

/**
 * tracefs_instance_set_buffer_size - modify the ring buffer size
 * @instance: The instance to modify (NULL for the top level)
 * @size: The size in kilobytes to to set the size to
 * @cpu: the CPU to set it to (-1 for all CPUs)
 *
 * Sets the size of the ring buffer per CPU buffers. If @cpu is negative,
 * then it sets the ring buffer size for all the per CPU buffers, otherwise
 * it only sets the per CPU buffer specified by @cpu.
 *
 * Returns 0 on success and -1 on error.
 */
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_subbuf_size - return the sub-buffer size of the ring buffer
 * @instance: The instance to get the buffer size from
 *
 * Returns the sub-buffer size in kilobytes.
 * Returns -1 on error.
 */
ssize_t tracefs_instance_get_subbuf_size(struct tracefs_instance *instance)
{
	long long size;
	int ret;

	ret = tracefs_instance_file_read_number(instance, "buffer_subbuf_size_kb", &size);
	if (ret < 0)
		return ret;

	return size;
}

/**
 * tracefs_instance_set_buffer_size - modify the ring buffer sub-buffer size
 * @instance: The instance to modify (NULL for the top level)
 * @size: The size in kilobytes to to set the sub-buffer size to
 *
 * Sets the sub-buffer size in kilobytes for the given ring buffer.
 *
 * Returns 0 on success and -1 on error.
 */
int tracefs_instance_set_subbuf_size(struct tracefs_instance *instance, size_t size)
{
	int ret;

	ret = tracefs_instance_file_write_number(instance, "buffer_subbuf_size_kb", size);

	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_write_number - Write integer from a trace file.
 * @instance: ftrace instance, can be NULL for the top instance
 * @file: name of the file
 * @res: The integer to write to @file
 *
 * Returns 0 if the write succeeds, -1 on error.
 */
int tracefs_instance_file_write_number(struct tracefs_instance *instance,
				       const char *file, size_t val)
{
	char buf[64];
	int ret;

	snprintf(buf, 64, "%zd\n", val);

	ret = tracefs_instance_file_write(instance, file, buf);

	return ret > 1 ? 0 : -1;
}

/**
 * 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 = tfs_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 = tfs_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 = tfs_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;
}

static int clear_trigger(const char *file)
{
	char trigger[BUFSIZ];
	char *save = NULL;
	char *line;
	char *buf;
	int size;
	int len;
	int ret;

	size = tfs_str_read_file(file, &buf, true);
	if (size < 1)
		return 0;

	trigger[0] = '!';

	for (line = strtok_r(buf, "\n", &save); line; line = strtok_r(NULL, "\n", &save)) {
		if (line[0] == '#')
			continue;
		len = strlen(line);
		if (len > BUFSIZ - 2)
			len = BUFSIZ - 2;
		strncpy(trigger + 1, line, len);
		trigger[len + 1] = '\0';
		/* We don't want any filters or extra on the line */
		strtok(trigger, " ");
		write_file(file, trigger, O_WRONLY);
	}

	free(buf);

	/*
	 * Some triggers have an order in removing them.
	 * They will not be removed if done in the wrong order.
	 */
	size = tfs_str_read_file(file, &buf, true);
	if (size < 1)
		return 0;

	ret = 0;
	for (line = strtok(buf, "\n"); line; line = strtok(NULL, "\n")) {
		if (line[0] == '#')
			continue;
		ret = 1;
		break;
	}
	free(buf);
	return ret;
}

static void disable_func_stack_trace_instance(struct tracefs_instance *instance)
{
	char *content;
	char *cond;
	int size;

	content = tracefs_instance_file_read(instance, "current_tracer", &size);
	if (!content)
		return;
	cond = tfs_strstrip(content);
	if (memcmp(cond, "function", size - (cond - content)) != 0)
		goto out;

	tracefs_option_disable(instance, TRACEFS_OPTION_FUNC_STACKTRACE);
 out:
	free(content);
}

static void reset_cpu_mask(struct tracefs_instance *instance)
{
	int cpus = sysconf(_SC_NPROCESSORS_CONF);
	int fullwords = (cpus - 1) / 32;
	int bits = (cpus - 1) % 32 + 1;
	int len = (fullwords + 1) * 9;
	char buf[len + 1];

	buf[0] = '\0';
	sprintf(buf, "%x", (unsigned int)((1ULL << bits) - 1));
	while (fullwords-- > 0)
		strcat(buf, ",ffffffff");

	tracefs_instance_file_write(instance, "tracing_cpumask", buf);
}

static void clear_func_filter(struct tracefs_instance *instance, const char *file)
{
	char filter[BUFSIZ];
	char *line;
	char *buf;
	char *p;
	int len;

	buf = tracefs_instance_file_read(instance, file, NULL);
	if (!buf)
		return;

	/* Now remove filters */
	filter[0] = '!';

	/*
	 * To delete a filter, we need to write a '!filter'
	 * to the file for each filter.
	 */
	for (line = strtok(buf, "\n"); line; line = strtok(NULL, "\n")) {
		if (line[0] == '#')
			continue;
		len = strlen(line);
		if (len > BUFSIZ - 2)
			len = BUFSIZ - 2;

		strncpy(filter + 1, line, len);
		filter[len + 1] = '\0';
		/*
		 * To remove "unlimited" filters, we must remove
		 * the ":unlimited" from what we write.
		 */
		p = strstr(filter, ":unlimited");
		if (p) {
			*p = '\0';
			len = p - filter;
		}
		/*
		 * The write to this file expects white space
		 * at the end :-p
		 */
		filter[len] = '\n';
		filter[len+1] = '\0';
		tracefs_instance_file_append(instance, file, filter);
	}
	free(buf);
}

static void clear_func_filters(struct tracefs_instance *instance)
{
	int i;
	const char * const files[] = { "set_ftrace_filter",
				       "set_ftrace_notrace",
				       "set_graph_function",
				       "set_graph_notrace",
				       "stack_trace_filter",
				       NULL };

	for (i = 0; files[i]; i++)
		clear_func_filter(instance, files[i]);
}

/**
 * tracefs_instance_clear - clear the trace buffer
 * @instance: The instance to clear the trace for.
 *
 * Returns 0 on succes, -1 on error
 */
int tracefs_instance_clear(struct tracefs_instance *instance)
{
	return tracefs_instance_file_clear(instance, "trace");
}

/**
 * tracefs_instance_reset - Reset a ftrace instance to its default state
 * @instance - a ftrace instance to be reseted
 *
 * The main logic and the helper functions are copied from
 * trace-cmd/tracecmd/trace-record.c, trace_reset()
 */
void tracefs_instance_reset(struct tracefs_instance *instance)
{
	int has_trigger = -1;
	char **systems;
	struct stat st;
	char **file_list = NULL;
	int list_size = 0;
	char **events;
	char *file;
	int i, j;
	int ret;

	tracefs_trace_off(instance);
	disable_func_stack_trace_instance(instance);
	tracefs_tracer_clear(instance);
	tracefs_instance_file_write(instance, "events/enable", "0");
	tracefs_instance_file_write(instance, "set_ftrace_pid", "");
	tracefs_instance_file_write(instance, "max_graph_depth", "0");
	tracefs_instance_file_clear(instance, "trace");

	systems = tracefs_event_systems(NULL);
	if (systems) {
		for (i = 0; systems[i]; i++) {
			events = tracefs_system_events(NULL, systems[i]);
			if (!events)
				continue;
			for (j = 0; events[j]; j++) {
				file = tracefs_event_get_file(instance, systems[i],
							      events[j], "filter");
				write_file(file, "0", O_WRONLY | O_TRUNC);
				tracefs_put_tracing_file(file);

				file = tracefs_event_get_file(instance, systems[i],
							      events[j], "trigger");
				if (has_trigger < 0) {
					/* Check if the kernel is configured with triggers */
					if (stat(file, &st) < 0)
						has_trigger = 0;
					else
						has_trigger = 1;
				}
				if (has_trigger) {
					ret = clear_trigger(file);
					if (ret) {
						char **list;
						list = tracefs_list_add(file_list, file);
						if (list)
							file_list = list;
					}
				}
				tracefs_put_tracing_file(file);
			}
			tracefs_list_free(events);
		}
		tracefs_list_free(systems);
	}

	while (file_list && list_size != tracefs_list_size(file_list)) {
		char **list = file_list;

		list_size = tracefs_list_size(file_list);
		file_list = NULL;
		for (i = 0; list[i]; i++) {
			file = list[i];
			ret = clear_trigger(file);
			if (ret) {
				char **tlist;
				tlist = tracefs_list_add(file_list, list[i]);
				if (tlist)
					file_list = tlist;
			}
		}
		tracefs_list_free(list);
	}
	tracefs_list_free(file_list);

	tracefs_instance_file_write(instance, "synthetic_events", " ");
	tracefs_instance_file_write(instance, "error_log", " ");
	tracefs_instance_file_write(instance, "trace_clock", "local");
	tracefs_instance_file_write(instance, "set_event_pid", "");
	reset_cpu_mask(instance);
	clear_func_filters(instance);
	tracefs_instance_file_write(instance, "tracing_max_latency", "0");
	tracefs_trace_on(instance);
}
