// SPDX-License-Identifier: LGPL-2.1
/*
 * Copyright (C) 2008, 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
 *
 * Updates:
 * Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com>
 *
 */
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <regex.h>
#include <dirent.h>
#include <limits.h>
#include <pthread.h>
#include <errno.h>

#include "tracefs.h"
#include "tracefs-local.h"

__hidden pthread_mutex_t toplevel_lock = PTHREAD_MUTEX_INITIALIZER;

#define TRACE_CTRL		"tracing_on"
#define TRACE_FILTER		"set_ftrace_filter"
#define TRACE_NOTRACE		"set_ftrace_notrace"
#define TRACE_FILTER_LIST	"available_filter_functions"
#define CUR_TRACER		"current_tracer"

#define TRACERS \
	C(NOP,                  "nop"),			\
	C(CUSTOM,		"CUSTOM"),		\
	C(FUNCTION,             "function"),            \
	C(FUNCTION_GRAPH,       "function_graph"),      \
	C(IRQSOFF,              "irqsoff"),             \
	C(PREEMPTOFF,           "preemptoff"),          \
	C(PREEMPTIRQSOFF,       "preemptirqsoff"),      \
	C(WAKEUP,               "wakeup"),              \
	C(WAKEUP_RT,            "wakeup_rt"),	\
	C(WAKEUP_DL,            "wakeup_dl"),           \
	C(MMIOTRACE,            "mmiotrace"),           \
	C(HWLAT,                "hwlat"),               \
	C(BRANCH,               "branch"),              \
	C(BLOCK,                "block")

#undef C
#define C(a, b) b
const char *tracers[] = { TRACERS };

#undef C
#define C(a, b) TRACEFS_TRACER_##a
const int tracer_enums[] = { TRACERS };

/* File descriptor for Top level set_ftrace_filter  */
static int ftrace_filter_fd = -1;
static int ftrace_notrace_fd = -1;

static const char * const options_map[] = {
	"unknown",
	"annotate",
	"bin",
	"blk_cgname",
	"blk_cgroup",
	"blk_classic",
	"block",
	"context-info",
	"disable_on_free",
	"display-graph",
	"event-fork",
	"funcgraph-abstime",
	"funcgraph-cpu",
	"funcgraph-duration",
	"funcgraph-irqs",
	"funcgraph-overhead",
	"funcgraph-overrun",
	"funcgraph-proc",
	"funcgraph-tail",
	"func_stack_trace",
	"function-fork",
	"function-trace",
	"graph-time",
	"hex",
	"irq-info",
	"latency-format",
	"markers",
	"overwrite",
	"pause-on-trace",
	"printk-msg-only",
	"print-parent",
	"raw",
	"record-cmd",
	"record-tgid",
	"sleep-time",
	"stacktrace",
	"sym-addr",
	"sym-offset",
	"sym-userobj",
	"trace_printk",
	"userstacktrace",
	"verbose" };

static int trace_on_off(int fd, bool on)
{
	const char *val = on ? "1" : "0";
	int ret;

	ret = write(fd, val, 1);
	if (ret == 1)
		return 0;

	return -1;
}

static int trace_on_off_file(struct tracefs_instance *instance, bool on)
{
	int ret;
	int fd;

	fd = tracefs_instance_file_open(instance, TRACE_CTRL, O_WRONLY);
	if (fd < 0)
		return -1;
	ret = trace_on_off(fd, on);
	close(fd);

	return ret;
}

/**
 * tracefs_trace_is_on - Check if writing traces to the ring buffer is enabled
 * @instance: ftrace instance, can be NULL for the top instance
 *
 * Returns -1 in case of an error, 0 if tracing is disable or 1 if tracing
 * is enabled.
 */
int tracefs_trace_is_on(struct tracefs_instance *instance)
{
	long long res;

	if (tracefs_instance_file_read_number(instance, TRACE_CTRL, &res) == 0)
		return (int)res;

	return -1;
}

/**
 * tracefs_trace_on - Enable writing traces to the ring buffer of the given instance
 * @instance: ftrace instance, can be NULL for the top instance
 *
 * Returns -1 in case of an error or 0 otherwise
 */
int tracefs_trace_on(struct tracefs_instance *instance)
{
	return trace_on_off_file(instance, true);
}

/**
 * tracefs_trace_off - Disable writing traces to the ring buffer of the given instance
 * @instance: ftrace instance, can be NULL for the top instance
 *
 * Returns -1 in case of an error or 0 otherwise
 */
int tracefs_trace_off(struct tracefs_instance *instance)
{
	return trace_on_off_file(instance, false);
}

/**
 * tracefs_trace_on_fd - Enable writing traces to the ring buffer
 * @fd: File descriptor to ftrace tracing_on file, previously opened
 *	with tracefs_trace_on_get_fd()
 *
 * Returns -1 in case of an error or 0 otherwise
 */
int tracefs_trace_on_fd(int fd)
{
	if (fd < 0)
		return -1;
	return trace_on_off(fd, true);
}

/**
 * tracefs_trace_off_fd - Disable writing traces to the ring buffer
 * @fd: File descriptor to ftrace tracing_on file, previously opened
 *	with tracefs_trace_on_get_fd()
 *
 * Returns -1 in case of an error or 0 otherwise
 */
int tracefs_trace_off_fd(int fd)
{
	if (fd < 0)
		return -1;
	return trace_on_off(fd, false);
}

/**
 * tracefs_option_name - Get trace option name from id
 * @id: trace option id
 *
 * Returns string with option name, or "unknown" in case of not known option id.
 * The returned string must *not* be freed.
 */
const char *tracefs_option_name(enum tracefs_option_id id)
{
	/* Make sure options map contains all the options */
	BUILD_BUG_ON(ARRAY_SIZE(options_map) != TRACEFS_OPTION_MAX);

	if (id < TRACEFS_OPTION_MAX)
		return options_map[id];

	return options_map[0];
}

/**
 * tracefs_option_id - Get trace option ID from name
 * @name: trace option name
 *
 * Returns trace option ID or TRACEFS_OPTION_INVALID in case of an error or
 * unknown option name.
 */
enum tracefs_option_id tracefs_option_id(const char *name)
{
	int i;

	if (!name)
		return TRACEFS_OPTION_INVALID;

	for (i = 0; i < TRACEFS_OPTION_MAX; i++) {
		if (strlen(name) == strlen(options_map[i]) &&
		    !strcmp(options_map[i], name))
			return i;
	}

	return TRACEFS_OPTION_INVALID;
}

const static struct tracefs_options_mask *
trace_get_options(struct tracefs_instance *instance, bool enabled)
{
	pthread_mutex_t *lock = trace_get_lock(instance);
	struct tracefs_options_mask *bitmask;
	enum tracefs_option_id id;
	unsigned long long set;
	char file[PATH_MAX];
	struct stat st;
	long long val;
	char *path;
	int ret;

	bitmask = enabled ? enabled_opts_mask(instance) :
			   supported_opts_mask(instance);

	for (id = 1; id < TRACEFS_OPTION_MAX; id++) {
		snprintf(file, PATH_MAX, "options/%s", options_map[id]);
		path = tracefs_instance_get_file(instance, file);
		if (!path)
			return NULL;

		set = 1;
		ret = stat(path, &st);
		if (ret < 0 || !S_ISREG(st.st_mode)) {
			set = 0;
		} else if (enabled) {
			ret = tracefs_instance_file_read_number(instance, file, &val);
			if (ret != 0 || val != 1)
				set = 0;
		}

		pthread_mutex_lock(lock);
		bitmask->mask = (bitmask->mask & ~(1ULL << (id - 1))) | (set << (id - 1));
		pthread_mutex_unlock(lock);

		tracefs_put_tracing_file(path);
	}


	return bitmask;
}

/**
 * tracefs_options_get_supported - Get all supported trace options in given instance
 * @instance: ftrace instance, can be NULL for the top instance
 *
 * Returns bitmask structure with all trace options, supported in given instance,
 * or NULL in case of an error.
 */
const struct tracefs_options_mask *
tracefs_options_get_supported(struct tracefs_instance *instance)
{
	return trace_get_options(instance, false);
}

/**
 * tracefs_options_get_enabled - Get all currently enabled trace options in given instance
 * @instance: ftrace instance, can be NULL for the top instance
 *
 * Returns bitmask structure with all trace options, enabled in given instance,
 * or NULL in case of an error.
 */
const struct tracefs_options_mask *
tracefs_options_get_enabled(struct tracefs_instance *instance)
{
	return trace_get_options(instance, true);
}

static int trace_config_option(struct tracefs_instance *instance,
			       enum tracefs_option_id id, bool set)
{
	const char *set_str = set ? "1" : "0";
	char file[PATH_MAX];
	const char *name;

	name = tracefs_option_name(id);
	if (!name)
		return -1;

	snprintf(file, PATH_MAX, "options/%s", name);
	if (strlen(set_str) != tracefs_instance_file_write(instance, file, set_str))
		return -1;
	return 0;
}

/**
 * tracefs_option_enable - Enable trace option
 * @instance: ftrace instance, can be NULL for the top instance
 * @id: trace option id
 *
 * Returns -1 in case of an error or 0 otherwise
 */
int tracefs_option_enable(struct tracefs_instance *instance, enum tracefs_option_id id)
{
	return trace_config_option(instance, id, true);
}

/**
 * tracefs_option_disable - Disable trace option
 * @instance: ftrace instance, can be NULL for the top instance
 * @id: trace option id
 *
 * Returns -1 in case of an error or 0 otherwise
 */
int tracefs_option_disable(struct tracefs_instance *instance, enum tracefs_option_id id)
{
	return trace_config_option(instance, id, false);
}

/**
 * tracefs_option_is_supported - Check if an option is supported
 * @instance: ftrace instance, can be NULL for the top instance
 * @id: trace option id
 *
 * Returns true if an option with given id is supported by the system, false if
 * it is not supported.
 */
bool tracefs_option_is_supported(struct tracefs_instance *instance, enum tracefs_option_id id)
{
	const char *name = tracefs_option_name(id);
	char file[PATH_MAX];

	if (!name)
		return false;
	snprintf(file, PATH_MAX, "options/%s", name);
	return tracefs_file_exists(instance, file);
}

/**
 * tracefs_option_is_enabled - Check if an option is enabled in given instance
 * @instance: ftrace instance, can be NULL for the top instance
 * @id: trace option id
 *
 * Returns true if an option with given id is enabled in the given instance,
 * false if it is not enabled.
 */
bool tracefs_option_is_enabled(struct tracefs_instance *instance, enum tracefs_option_id id)
{
	const char *name = tracefs_option_name(id);
	char file[PATH_MAX];
	long long res;

	if (!name)
		return false;
	snprintf(file, PATH_MAX, "options/%s", name);
	if (!tracefs_instance_file_read_number(instance, file, &res) && res)
		return true;

	return false;
}

/**
 * tracefs_option_mask_is_set - Check if given option is set in the bitmask
 * @options: Options bitmask
 * @id: trace option id
 *
 * Returns true if an option with given id is set in the bitmask,
 * false if it is not set.
 */
bool tracefs_option_mask_is_set(const struct tracefs_options_mask *options,
			   enum tracefs_option_id id)
{
	if (id > TRACEFS_OPTION_INVALID)
		return options->mask & (1ULL << (id - 1));
	return false;
}

struct func_list {
	struct func_list	*next;
	char			*func;
	unsigned int		start;
	unsigned int		end;
};

struct func_filter {
	const char		*filter;
	regex_t			re;
	bool			set;
	bool			is_regex;
};

static bool is_regex(const char *str)
{
	int i;

	for (i = 0; str[i]; i++) {
		switch (str[i]) {
		case 'a' ... 'z':
		case 'A'...'Z':
		case '_':
		case '0'...'9':
		case '*':
		case '.':
			/* Dots can be part of a function name */
		case '?':
			continue;
		default:
			return true;
		}
	}
	return false;
}

static char *update_regex(const char *reg)
{
	int len = strlen(reg);
	char *str;

	if (reg[0] == '^' && reg[len - 1] == '$')
		return strdup(reg);

	str = malloc(len + 3);
	if (reg[0] == '^') {
		strcpy(str, reg);
	} else {
		str[0] = '^';
		strcpy(str + 1, reg);
		len++; /* add ^ */
	}
	if (str[len - 1] != '$')
		str[len++]= '$';
	str[len] = '\0';
	return str;
}

/*
 * Convert a glob into a regular expression.
 */
static char *make_regex(const char *glob)
{
	char *str;
	int cnt = 0;
	int i, j;

	for (i = 0; glob[i]; i++) {
		if (glob[i] == '*'|| glob[i] == '.')
			cnt++;
	}

	/* '^' + ('*'->'.*' or '.' -> '\.') + '$' + '\0' */
	str = malloc(i + cnt + 3);
	if (!str)
		return NULL;

	str[0] = '^';
	for (i = 0, j = 1; glob[i]; i++, j++) {
		if (glob[i] == '*')
			str[j++] = '.';
		/* Dots can be part of a function name */
		if (glob[i] == '.')
			str[j++] = '\\';
		str[j] = glob[i];
	}
	str[j++] = '$';
	str[j] = '\0';
	return str;
}

static bool match(const char *str, struct func_filter *func_filter)
{
	return regexec(&func_filter->re, str, 0, NULL, 0) == 0;
}

/*
 * Return 0 on success, -1 error writing, 1 on other errors.
 */
static int write_filter(int fd, const char *filter, const char *module)
{
	char *each_str = NULL;
	int write_size;
	int size;

	if (module)
		write_size = asprintf(&each_str, "%s:mod:%s ", filter, module);
	else
		write_size = asprintf(&each_str, "%s ", filter);

	if (write_size < 0)
		return 1;

	size = write(fd, each_str, write_size);
	free(each_str);

	/* compare written bytes*/
	if (size < write_size)
		return -1;

	return 0;
}

static int add_func(struct func_list ***next_func_ptr, unsigned int index)
{
	struct func_list **next_func = *next_func_ptr;
	struct func_list *func_list = *next_func;

	if (!func_list) {
		func_list = calloc(1, sizeof(*func_list));
		if (!func_list)
			return -1;
		func_list->start = index;
		func_list->end = index;
		*next_func = func_list;
		return 0;
	}

	if (index == func_list->end + 1) {
		func_list->end = index;
		return 0;
	}
	*next_func_ptr = &func_list->next;
	return add_func(next_func_ptr, index);
}

static int add_func_str(struct func_list ***next_func_ptr, const char *func)
{
	struct func_list **next_func = *next_func_ptr;
	struct func_list *func_list = *next_func;

	if (!func_list) {
		func_list = calloc(1, sizeof(*func_list));
		if (!func_list)
			return -1;
		func_list->func = strdup(func);
		if (!func_list->func)
			return -1;
		*next_func = func_list;
		return 0;
	}
	*next_func_ptr = &func_list->next;
	return add_func_str(next_func_ptr, func);
}

static void free_func_list(struct func_list *func_list)
{
	struct func_list *f;

	while (func_list) {
		f = func_list;
		func_list = f->next;
		free(f->func);
		free(f);
	}
}

enum match_type {
	FILTER_CHECK	= (1 << 0),
	FILTER_WRITE	= (1 << 1),
	FILTER_FUTURE	= (1 << 2),
	SAVE_STRING	= (1 << 2),
};

static int match_filters(int fd, struct func_filter *func_filter,
			 const char *module, struct func_list **func_list,
			 int flags)
{
	enum match_type type = flags & (FILTER_CHECK | FILTER_WRITE);
	bool save_str = flags & SAVE_STRING;
	bool future = flags & FILTER_FUTURE;
	bool mod_match = false;
	char *line = NULL;
	size_t size = 0;
	char *path;
	FILE *fp;
	int index = 0;
	int ret = 1;
	int mlen;

	path = tracefs_get_tracing_file(TRACE_FILTER_LIST);
	if (!path)
		return 1;

	fp = fopen(path, "r");
	tracefs_put_tracing_file(path);

	if (!fp)
		return 1;

	if (module)
		mlen = strlen(module);

	while (getline(&line, &size, fp) >= 0) {
		char *saveptr = NULL;
		char *tok, *mtok;
		int len = strlen(line);

		if (line[len - 1] == '\n')
			line[len - 1] = '\0';
		tok = strtok_r(line, " ", &saveptr);
		if (!tok)
			goto next;

		index++;

		if (module) {
			mtok = strtok_r(NULL, " ", &saveptr);
			if (!mtok)
				goto next;
			if ((strncmp(mtok + 1, module, mlen) != 0) ||
			    (mtok[mlen + 1] != ']'))
				goto next;
			if (future)
				mod_match = true;
		}
		switch (type) {
		case FILTER_CHECK:
			if (match(tok, func_filter)) {
				func_filter->set = true;
				if (save_str)
					ret = add_func_str(&func_list, tok);
				else
					ret = add_func(&func_list, index);
				if (ret)
					goto out;
			}
			break;
		case FILTER_WRITE:
			/* Writes only have one filter */
			if (match(tok, func_filter)) {
				ret = write_filter(fd, tok, module);
				if (ret)
					goto out;
			}
			break;
		default:
			/* Should never happen */
			ret = -1;
			goto out;

		}
	next:
		free(line);
		line = NULL;
		len = 0;
	}
 out:
	free(line);
	fclose(fp);

	/* If there was no matches and future was set, this is a success */
	if (future && !mod_match)
		ret = 0;

	return ret;
}

static int check_available_filters(struct func_filter *func_filter,
				   const char *module,
				   struct func_list **func_list,
				   bool future)
{
	int flags = FILTER_CHECK | (future ? FILTER_FUTURE : 0);

	return match_filters(-1, func_filter, module, func_list, flags);
}


static int list_available_filters(struct func_filter *func_filter,
				   const char *module,
				   struct func_list **func_list)
{
	int flags = FILTER_CHECK | SAVE_STRING;

	return match_filters(-1, func_filter, module, func_list, flags);
}

static int set_regex_filter(int fd, struct func_filter *func_filter,
			    const char *module)
{
	return match_filters(fd, func_filter, module, NULL, FILTER_WRITE);
}

static int controlled_write(int fd, struct func_filter *func_filter,
			    const char *module)
{
	const char *filter = func_filter->filter;
	int ret;

	if (func_filter->is_regex)
		ret = set_regex_filter(fd, func_filter, module);
	else
		ret = write_filter(fd, filter, module);

	return ret;
}

static int init_func_filter(struct func_filter *func_filter, const char *filter)
{
	char *str;
	int ret;

	if (!(func_filter->is_regex = is_regex(filter)))
		str = make_regex(filter);
	else
		str = update_regex(filter);

	if (!str)
		return -1;

	ret = regcomp(&func_filter->re, str, REG_ICASE|REG_NOSUB);
	free(str);

	if (ret < 0)
		return -1;

	func_filter->filter = filter;
	return 0;
}

static int write_number(int fd, unsigned int start, unsigned int end)
{
	char buf[64];
	unsigned int i;
	int n, ret;

	for (i = start; i <= end; i++) {
		n = snprintf(buf, 64, "%d ", i);
		ret = write(fd, buf, n);
		if (ret < 0)
			return ret;
	}

	return 0;
}

/*
 * This will try to write the first number, if that fails, it
 * will assume that it is not supported and return 1.
 * If the first write succeeds, but a following write fails, then
 * the kernel does support this, but something else went wrong,
 * in this case, return -1.
 */
static int write_func_list(int fd, struct func_list *list)
{
	int ret;

	if (!list)
		return 0;

	ret = write_number(fd, list->start, list->end);
	if (ret)
		return 1; // try a different way
	list = list->next;
	while (list) {
		ret = write_number(fd, list->start, list->end);
		if (ret)
			return -1;
		list = list->next;
	}
	return 0;
}

static int update_filter(const char *filter_path, int *fd,
			 struct tracefs_instance *instance, const char *filter,
			 const char *module, unsigned int flags)
{
	struct func_filter func_filter;
	struct func_list *func_list = NULL;
	bool reset = flags & TRACEFS_FL_RESET;
	bool cont = flags & TRACEFS_FL_CONTINUE;
	bool future = flags & TRACEFS_FL_FUTURE;
	pthread_mutex_t *lock = trace_get_lock(instance);
	int open_flags;
	int ret = 1;

	/* future flag is only applicable to modules */
	if (future && !module) {
		errno = EINVAL;
		return 1;
	}

	pthread_mutex_lock(lock);

	/* RESET is only allowed if the file is not opened yet */
	if (reset && *fd >= 0) {
		errno = EBUSY;
		ret = -1;
		goto out;
	}

	/*
	 * Set EINVAL on no matching filter. But errno may still be modified
	 * on another type of failure (allocation or opening a file).
	 */
	errno = EINVAL;

	/* module set with NULL filter means to enable all functions in a module */
	if (module && !filter)
		filter = "*";

	if (!filter) {
		/* OK to call without filters if this is closing the opened file */
		if (!cont && *fd >= 0) {
			errno = 0;
			ret = 0;
			close(*fd);
			*fd = -1;
		}
		/* Also OK to call if reset flag is set */
		if (reset)
			goto open_file;

		goto out;
	}

	if (init_func_filter(&func_filter, filter) < 0)
		goto out;

	ret = check_available_filters(&func_filter, module, &func_list, future);
	if (ret)
		goto out_free;

 open_file:
	ret = 1;

	open_flags = reset ? O_TRUNC : O_APPEND;

	if (*fd < 0)
		*fd = open(filter_path, O_WRONLY | O_CLOEXEC | open_flags);
	if (*fd < 0)
		goto out_free;

	errno = 0;
	ret = 0;

	if (filter) {
		/*
		 * If future is set, and no functions were found, then
		 * set it directly.
		 */
		if (func_list)
			ret = write_func_list(*fd, func_list);
		else
			ret = 1;
		if (ret > 0)
			ret = controlled_write(*fd, &func_filter, module);
	}

	if (!cont) {
		close(*fd);
		*fd = -1;
	}

 out_free:
	if (filter)
		regfree(&func_filter.re);
	free_func_list(func_list);
 out:
	pthread_mutex_unlock(lock);

	return ret;
}

/**
 * tracefs_function_filter - filter the functions that are traced
 * @instance: ftrace instance, can be NULL for top tracing instance.
 * @filter: The filter to filter what functions are to be traced
 * @module: Module to be traced or NULL if all functions are to be examined.
 * @flags: flags on modifying the filter file
 *
 * @filter may be a full function name, a glob, or a regex. It will be
 * considered a regex, if there's any characters that are not normally in
 * function names or "*" or "?" for a glob.
 *
 * @flags:
 *   TRACEFS_FL_RESET - will clear the functions in the filter file
 *          before applying the @filter. This will error with -1
 *          and errno of EBUSY if this flag is set and a previous
 *          call had the same instance and TRACEFS_FL_CONTINUE set.
 *   TRACEFS_FL_CONTINUE - will keep the filter file open on return.
 *          The filter is updated on closing of the filter file.
 *          With this flag set, the file is not closed, and more filters
 *          may be added before they take effect. The last call of this
 *          function must be called without this flag for the filter
 *          to take effect.
 *   TRACEFS_FL_FUTURE - only applicable if "module" is set. If no match
 *          is made, and the module is not yet loaded, it will still attempt
 *          to write the filter plus the module; "<filter>:mod:<module>"
 *          to the filter file. Starting with Linux kernels 4.13, it is possible
 *          to load the filter file with module functions for a module that
 *          is not yet loaded, and when the module is loaded, it will then
 *          activate the module.
 *
 * Returns 0 on success, 1 if there was an error but the filtering has not
 *  yet started, -1 if there was an error but the filtering has started.
 *  If -1 is returned and TRACEFS_FL_CONTINUE was set, then this function
 *  needs to be called again without the TRACEFS_FL_CONTINUE flag to commit
 *  the changes and close the filter file.
 */
int tracefs_function_filter(struct tracefs_instance *instance, const char *filter,
			    const char *module, unsigned int flags)
{
	char *filter_path;
	int *fd;
	int ret;

	filter_path = tracefs_instance_get_file(instance, TRACE_FILTER);
	if (!filter_path)
		return -1;

	if (instance)
		fd = &instance->ftrace_filter_fd;
	else
		fd = &ftrace_filter_fd;

	ret = update_filter(filter_path, fd, instance, filter, module, flags);
	tracefs_put_tracing_file(filter_path);
	return ret;
}

/**
 * tracefs_function_notrace - filter the functions that are not to be traced
 * @instance: ftrace instance, can be NULL for top tracing instance.
 * @filter: The filter to filter what functions are not to be traced
 * @module: Module to be traced or NULL if all functions are to be examined.
 * @flags: flags on modifying the filter file
 *
 * See tracefs_function_filter, as this has the same functionality but
 * for adding to the "notrace" filter.
 */
int tracefs_function_notrace(struct tracefs_instance *instance, const char *filter,
			     const char *module, unsigned int flags)
{
	char *filter_path;
	int *fd;
	int ret;

	filter_path = tracefs_instance_get_file(instance, TRACE_NOTRACE);
	if (!filter_path)
		return -1;

	if (instance)
		fd = &instance->ftrace_notrace_fd;
	else
		fd = &ftrace_notrace_fd;

	ret = update_filter(filter_path, fd, instance, filter, module, flags);
	tracefs_put_tracing_file(filter_path);
	return ret;
}

int write_tracer(int fd, const char *tracer)
{
	int ret;

	ret = write(fd, tracer, strlen(tracer));
	if (ret < strlen(tracer))
		return -1;
	return ret;
}

/**
 * tracefs_set_tracer - function to set the tracer
 * @instance: ftrace instance, can be NULL for top tracing instance
 * @tracer: The tracer enum that defines the tracer to be set
 * @t: A tracer name if TRACEFS_TRACER_CUSTOM is passed in for @tracer
 *
 * Set the tracer for the instance based on the tracefs_tracer enums.
 * If the user wishes to enable a tracer that is not defined by
 * the enum (new or custom kernel), the tracer can be set to
 * TRACEFS_TRACER_CUSTOM, and pass in a const char * name for
 * the tracer to set.
 *
 * Returns 0 on succes, negative on error.
 */

int tracefs_tracer_set(struct tracefs_instance *instance,
		       enum tracefs_tracers tracer, ...)
{
	char *tracer_path = NULL;
	const char *t = NULL;
	int ret = -1;
	int fd = -1;
	int i;

	if (tracer < 0 || tracer > ARRAY_SIZE(tracers)) {
		errno = EINVAL;
		return -1;
	}

	tracer_path = tracefs_instance_get_file(instance, CUR_TRACER);
	if (!tracer_path)
		return -1;

	fd = open(tracer_path, O_WRONLY);
	if (fd < 0) {
		errno = ENOENT;
		goto out;
	}

	if (tracer == TRACEFS_TRACER_CUSTOM) {
		va_list ap;

		va_start(ap, tracer);
		t = va_arg(ap, const char *);
		va_end(ap);
	} else if (tracer == tracer_enums[tracer]) {
		t = tracers[tracer];
	} else {
		for (i = 0; i < ARRAY_SIZE(tracer_enums); i++) {
			if (tracer == tracer_enums[i]) {
				t = tracers[i];
				break;
			}
		}
	}
	if (!t) {
		errno = EINVAL;
		goto out;
	}
	ret = write_tracer(fd, t);
	/*
	 * If the tracer does not exist, EINVAL is returned,
	 * but let the user know this as ENODEV.
	 */
	if (ret < 0 && errno == EINVAL)
		errno = ENODEV;
 out:
	tracefs_put_tracing_file(tracer_path);
	close(fd);
	return ret;
}

int  tracefs_tracer_clear(struct tracefs_instance *instance)
{
	return tracefs_tracer_set(instance, TRACEFS_TRACER_NOP);
}

static bool splice_safe(int fd, int pfd)
{
	int ret;

	errno = 0;
	ret = splice(pfd, NULL, fd, NULL,
		     10, SPLICE_F_NONBLOCK | SPLICE_F_MOVE);

	return !ret || (ret < 0 && errno == EAGAIN);
}

static ssize_t read_trace_pipe(bool *keep_going, int in_fd, int out_fd)
{
	char buf[BUFSIZ];
	ssize_t bread = 0;
	int ret;

	while (*(volatile bool *)keep_going) {
		int r;
		ret = read(in_fd, buf, BUFSIZ);
		if (ret <= 0)
			break;
		r = ret;
		ret = write(out_fd, buf, r);
		if (ret < 0)
			break;
		bread += ret;
		/*
		 * If the write does a partial write, then
		 * the iteration should stop. This can happen if
		 * the destination file system ran out of disk space.
		 * Sure, it probably lost a little from the read
		 * but there's not much more that can be
		 * done. Just return what was transferred.
		 */
		if (ret < r)
			break;
	}

	if (ret < 0 && (errno == EAGAIN || errno == EINTR))
		ret = 0;

	return ret < 0 ? ret : bread;
}

static bool top_pipe_keep_going;

/**
 * tracefs_trace_pipe_stream - redirect the stream of trace data to an output
 * file. The "splice" system call is used to moves the data without copying
 * between kernel address space and user address space. The user can interrupt
 * the streaming of the data by pressing Ctrl-c.
 * @fd: The file descriptor of the output file.
 * @instance: ftrace instance, can be NULL for top tracing instance.
 * @flags: flags for opening the trace_pipe file.
 *
 * Returns -1 in case of an error or number of bytes transferred otherwise.
 */
ssize_t tracefs_trace_pipe_stream(int fd, struct tracefs_instance *instance,
				 int flags)
{
	bool *keep_going = instance ? &instance->pipe_keep_going :
				      &top_pipe_keep_going;
	const char *file = "trace_pipe";
	int brass[2], in_fd, ret = -1;
	int sflags = flags & O_NONBLOCK ? SPLICE_F_NONBLOCK : 0;
	off_t data_size;
	ssize_t bread = 0;

	(*(volatile bool *)keep_going) = true;

	in_fd = tracefs_instance_file_open(instance, file, O_RDONLY | flags);
	if (in_fd < 0) {
		tracefs_warning("Failed to open 'trace_pipe'.");
		return ret;
	}

	if(pipe(brass) < 0) {
		tracefs_warning("Failed to open pipe.");
		goto close_file;
	}

	data_size = fcntl(brass[0], F_GETPIPE_SZ);
	if (data_size <= 0) {
		tracefs_warning("Failed to open pipe (size=0).");
		goto close_all;
	}

	/* Test if the output is splice safe */
	if (!splice_safe(fd, brass[0])) {
		bread = read_trace_pipe(keep_going, in_fd, fd);
		ret = 0; /* Force return of bread */
		goto close_all;
	}

	errno = 0;

	while (*(volatile bool *)keep_going) {
		ret = splice(in_fd, NULL,
			     brass[1], NULL,
			     data_size, sflags);
		if (ret < 0)
			break;

		ret = splice(brass[0], NULL,
			     fd, NULL,
			     data_size, sflags);
		if (ret < 0)
			break;
		bread += ret;
	}

	/*
	 * Do not return error in the case when the "splice" system call
	 * was interrupted by the user (pressing Ctrl-c).
	 * Or if NONBLOCK was specified.
	 */
	if (!keep_going || errno == EAGAIN || errno == EINTR)
		ret = 0;

 close_all:
	close(brass[0]);
	close(brass[1]);
 close_file:
	close(in_fd);

	return ret ? ret : bread;
}

/**
 * tracefs_trace_pipe_print - redirect the stream of trace data to "stdout".
 * The "splice" system call is used to moves the data without copying
 * between kernel address space and user address space.
 * @instance: ftrace instance, can be NULL for top tracing instance.
 * @flags: flags for opening the trace_pipe file.
 *
 * Returns -1 in case of an error or number of bytes transferred otherwise.
 */

ssize_t tracefs_trace_pipe_print(struct tracefs_instance *instance, int flags)
{
	return tracefs_trace_pipe_stream(STDOUT_FILENO, instance, flags);
}

/**
 * tracefs_trace_pipe_stop - stop the streaming of trace data.
 * @instance: ftrace instance, can be NULL for top tracing instance.
 */
void tracefs_trace_pipe_stop(struct tracefs_instance *instance)
{
	if (instance)
		instance->pipe_keep_going = false;
	else
		top_pipe_keep_going = false;
}

/**
 * tracefs_filter_functions - return a list of available functons that can be filtered
 * @filter: The filter to filter what functions to list (can be NULL for all)
 * @module: Module to be traced or NULL if all functions are to be examined.
 * @list: The list to return the list from (freed by tracefs_list_free() on success)
 *
 * Returns a list of function names that match @filter and @module. If both
 * @filter and @module is NULL, then all available functions that can be filtered
 * will be returned. (Note, there can be duplicates, if there are more than
 * one function with the same name.
 *
 * On success, zero is returned, and @list contains a list of functions that were
 * found, and must be freed with tracefs_list_free().
 * On failure, a negative number is returned, and @list is ignored.
 */
int tracefs_filter_functions(const char *filter, const char *module, char ***list)
{
	struct func_filter func_filter;
	struct func_list *func_list = NULL, *f;
	char **funcs = NULL;
	int ret;

	if (!filter)
		filter = ".*";

	ret = init_func_filter(&func_filter, filter);
	if (ret < 0)
		return ret;

	ret = list_available_filters(&func_filter, module, &func_list);
	if (ret < 0)
		goto out;

	ret = -1;
	for (f = func_list; f; f = f->next) {
		char **tmp;

		tmp = tracefs_list_add(funcs, f->func);
		if (!tmp) {
			tracefs_list_free(funcs);
			goto out;
		}
		funcs = tmp;
	}

	*list = funcs;
	ret = 0;
out:
	regfree(&func_filter.re);
	free_func_list(func_list);
	return ret;
}
