// 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 <dirent.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>

#include <kbuffer.h>

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

static struct follow_event *root_followers;
static int nr_root_followers;

static struct follow_event *root_missed_followers;
static int nr_root_missed_followers;

struct cpu_iterate {
	struct tracefs_cpu *tcpu;
	struct tep_record record;
	struct tep_event *event;
	struct kbuffer *kbuf;
	void *page;
	int psize;
	int cpu;
};

static int read_kbuf_record(struct cpu_iterate *cpu)
{
	unsigned long long ts;
	void *ptr;

	if (!cpu || !cpu->kbuf)
		return -1;
	ptr = kbuffer_read_event(cpu->kbuf, &ts);
	if (!ptr)
		return -1;

	memset(&cpu->record, 0, sizeof(cpu->record));
	cpu->record.ts = ts;
	cpu->record.size = kbuffer_event_size(cpu->kbuf);
	cpu->record.record_size = kbuffer_curr_size(cpu->kbuf);
	cpu->record.missed_events = kbuffer_missed_events(cpu->kbuf);
	cpu->record.cpu = cpu->cpu;
	cpu->record.data = ptr;
	cpu->record.ref_count = 1;

	kbuffer_next_event(cpu->kbuf, NULL);

	return 0;
}

int read_next_page(struct tep_handle *tep, struct cpu_iterate *cpu)
{
	enum kbuffer_long_size long_size;
	enum kbuffer_endian endian;
	int r;

	if (!cpu->tcpu)
		return -1;

	r = tracefs_cpu_buffered_read(cpu->tcpu, cpu->page, true);
	/*
	 * tracefs_cpu_buffered_read() only reads in full subbuffer size,
	 * but this wants partial buffers as well. If the function returns
	 * empty (-1 for EAGAIN), try tracefs_cpu_read() next, as that can
	 * read partially filled buffers too, but isn't as efficient.
	 */
	if (r <= 0)
		r = tracefs_cpu_read(cpu->tcpu, cpu->page, true);
	if (r <= 0)
		return -1;

	if (!cpu->kbuf) {
		if (tep_is_file_bigendian(tep))
			endian = KBUFFER_ENDIAN_BIG;
		else
			endian = KBUFFER_ENDIAN_LITTLE;

		if (tep_get_header_page_size(tep) == 8)
			long_size = KBUFFER_LSIZE_8;
		else
			long_size = KBUFFER_LSIZE_4;

		cpu->kbuf = kbuffer_alloc(long_size, endian);
		if (!cpu->kbuf)
			return -1;
	}

	kbuffer_load_subbuffer(cpu->kbuf, cpu->page);
	if (kbuffer_subbuffer_size(cpu->kbuf) > r) {
		tracefs_warning("%s: page_size > %d", __func__, r);
		return -1;
	}

	return 0;
}

int read_next_record(struct tep_handle *tep, struct cpu_iterate *cpu)
{
	int id;

	do {
		while (!read_kbuf_record(cpu)) {
			id = tep_data_type(tep, &(cpu->record));
			cpu->event = tep_find_event(tep, id);
			if (cpu->event)
				return 0;
		}
	} while (!read_next_page(tep, cpu));

	return -1;
}

/**
 * tracefs_follow_missed_events - Add callback for missed events for iterators
 * @instance: The instance to follow
 * @callback: The function to call when missed events is detected
 * @callback_data: The data to pass to @callback
 *
 * This attaches a callback to an @instance or the root instance if @instance
 * is NULL, where if tracefs_iterate_raw_events() is called, that if missed
 * events are detected, it will call @callback, with the following parameters:
 *  @event: The event pointer of the record with the missing events
 *  @record; The event instance of @event.
 *  @cpu: The cpu that the event happened on.
 *  @callback_data: The same as @callback_data passed to the function.
 *
 * If the count of missing events is available, @record->missed_events
 * will have a positive number holding the number of missed events since
 * the last event on the same CPU, or just -1 if that number is unknown
 * but missed events did happen.
 *
 * Returns 0 on success and -1 on error.
 */
int tracefs_follow_missed_events(struct tracefs_instance *instance,
				 int (*callback)(struct tep_event *,
						 struct tep_record *,
						 int, void *),
				 void *callback_data)
{
	struct follow_event **followers;
	struct follow_event *follower;
	struct follow_event follow;
	int *nr_followers;

	follow.event = NULL;
	follow.callback = callback;
	follow.callback_data = callback_data;

	if (instance) {
		followers = &instance->missed_followers;
		nr_followers = &instance->nr_missed_followers;
	} else {
		followers = &root_missed_followers;
		nr_followers = &nr_root_missed_followers;
	}
	follower = realloc(*followers, sizeof(*follower) *
			    ((*nr_followers) + 1));
	if (!follower)
		return -1;

	*followers = follower;
	follower[(*nr_followers)++] = follow;

	return 0;
}

static int call_missed_events(struct tracefs_instance *instance,
			      struct tep_event *event, struct tep_record *record, int cpu)
{
	struct follow_event *followers;
	int nr_followers;
	int ret = 0;
	int i;

	if (instance) {
		followers = instance->missed_followers;
		nr_followers = instance->nr_missed_followers;
	} else {
		followers = root_missed_followers;
		nr_followers = nr_root_missed_followers;
	}

	if (!followers)
		return 0;

	for (i = 0; i < nr_followers; i++) {
		ret |= followers[i].callback(event, record,
					     cpu, followers[i].callback_data);
	}

	return ret;
}

static int call_followers(struct tracefs_instance *instance,
			  struct tep_event *event, struct tep_record *record, int cpu)
{
	struct follow_event *followers;
	int nr_followers;
	int ret = 0;
	int i;

	if (record->missed_events)
		ret = call_missed_events(instance, event, record, cpu);
	if (ret)
		return ret;

	if (instance) {
		followers = instance->followers;
		nr_followers = instance->nr_followers;
	} else {
		followers = root_followers;
		nr_followers = nr_root_followers;
	}

	if (!followers)
		return 0;

	for (i = 0; i < nr_followers; i++) {
		if (followers[i].event == event)
			ret |= followers[i].callback(event, record,
						     cpu, followers[i].callback_data);
	}

	return ret;
}

static int read_cpu_pages(struct tep_handle *tep, struct tracefs_instance *instance,
			  struct cpu_iterate *cpus, int count,
			  int (*callback)(struct tep_event *,
					  struct tep_record *,
					  int, void *),
			  void *callback_context,
			  bool *keep_going)
{
	bool has_data = false;
	int ret;
	int i, j;

	for (i = 0; i < count; i++) {
		ret = read_next_record(tep, cpus + i);
		if (!ret)
			has_data = true;
	}

	while (has_data && *(volatile bool *)keep_going) {
		j = count;
		for (i = 0; i < count; i++) {
			if (!cpus[i].event)
				continue;
			if (j == count || cpus[j].record.ts > cpus[i].record.ts)
				j = i;
		}
		if (j < count) {
			if (call_followers(instance, cpus[j].event, &cpus[j].record, cpus[j].cpu))
				break;
			if (callback &&
			    callback(cpus[j].event, &cpus[j].record, cpus[j].cpu, callback_context))
				break;
			cpus[j].event = NULL;
			read_next_record(tep, cpus + j);
		} else {
			has_data = false;
		}
	}

	return 0;
}

static int open_cpu_files(struct tracefs_instance *instance, cpu_set_t *cpus,
			  int cpu_size, struct cpu_iterate **all_cpus, int *count)
{
	struct tracefs_cpu *tcpu;
	struct cpu_iterate *tmp;
	int nr_cpus;
	int cpu;
	int i = 0;

	*all_cpus = NULL;

	nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
	for (cpu = 0; cpu < nr_cpus; cpu++) {
		if (cpus && !CPU_ISSET_S(cpu, cpu_size, cpus))
			continue;
		tcpu = tracefs_cpu_open(instance, cpu, true);
		tmp = realloc(*all_cpus, (i + 1) * sizeof(*tmp));
		if (!tmp) {
			i--;
			goto error;
		}

		*all_cpus = tmp;

		memset(tmp + i, 0, sizeof(*tmp));

		if (!tcpu)
			goto error;

		tmp[i].tcpu = tcpu;
		tmp[i].cpu = cpu;
		tmp[i].psize = tracefs_cpu_read_size(tcpu);
		tmp[i].page =  malloc(tmp[i].psize);

		if (!tmp[i++].page)
			goto error;
	}
	*count = i;
	return 0;
 error:
	tmp = *all_cpus;
	for (; i >= 0; i--) {
		tracefs_cpu_close(tmp[i].tcpu);
		free(tmp[i].page);
	}
	free(tmp);
	*all_cpus = NULL;
	return -1;
}

/**
 * tracefs_follow_event - Add callback for specific events for iterators
 * @tep: a handle to the trace event parser context
 * @instance: The instance to follow
 * @system: The system of the event to track
 * @event_name: The name of the event to track
 * @callback: The function to call when the event is hit in an iterator
 * @callback_data: The data to pass to @callback
 *
 * This attaches a callback to an @instance or the root instance if @instance
 * is NULL, where if tracefs_iterate_raw_events() is called, that if the specified
 * event is hit, it will call @callback, with the following parameters:
 *  @event: The event pointer that was found by @system and @event_name.
 *  @record; The event instance of @event.
 *  @cpu: The cpu that the event happened on.
 *  @callback_data: The same as @callback_data passed to the function.
 *
 * Returns 0 on success and -1 on error.
 */
int tracefs_follow_event(struct tep_handle *tep, struct tracefs_instance *instance,
			  const char *system, const char *event_name,
			  int (*callback)(struct tep_event *,
					  struct tep_record *,
					  int, void *),
			  void *callback_data)
{
	struct follow_event **followers;
	struct follow_event *follower;
	struct follow_event follow;
	int *nr_followers;

	if (!tep) {
		errno = EINVAL;
		return -1;
	}

	follow.event = tep_find_event_by_name(tep, system, event_name);
	if (!follow.event) {
		errno = ENOENT;
		return -1;
	}

	follow.callback = callback;
	follow.callback_data = callback_data;

	if (instance) {
		followers = &instance->followers;
		nr_followers = &instance->nr_followers;
	} else {
		followers = &root_followers;
		nr_followers = &nr_root_followers;
	}
	follower = realloc(*followers, sizeof(*follower) *
			    ((*nr_followers) + 1));
	if (!follower)
		return -1;

	*followers = follower;
	follower[(*nr_followers)++] = follow;

	return 0;
}

static bool top_iterate_keep_going;

/*
 * tracefs_iterate_raw_events - Iterate through events in trace_pipe_raw,
 *				per CPU trace buffers
 * @tep: a handle to the trace event parser context
 * @instance: ftrace instance, can be NULL for the top instance
 * @cpus: Iterate only through the buffers of CPUs, set in the mask.
 *	  If NULL, iterate through all CPUs.
 * @cpu_size: size of @cpus set
 * @callback: A user function, called for each record from the file
 * @callback_context: A custom context, passed to the user callback function
 *
 * If the @callback returns non-zero, the iteration stops - in that case all
 * records from the current page will be lost from future reads
 * The events are iterated in sorted order, oldest first.
 *
 * Returns -1 in case of an error, or 0 otherwise
 */
int tracefs_iterate_raw_events(struct tep_handle *tep,
				struct tracefs_instance *instance,
				cpu_set_t *cpus, int cpu_size,
				int (*callback)(struct tep_event *,
						struct tep_record *,
						int, void *),
				void *callback_context)
{
	bool *keep_going = instance ? &instance->iterate_keep_going :
				      &top_iterate_keep_going;
	struct follow_event *followers;
	struct cpu_iterate *all_cpus;
	int count = 0;
	int ret;
	int i;

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

	if (!tep)
		return -1;

	if (instance)
		followers = instance->followers;
	else
		followers = root_followers;
	if (!callback && !followers)
		return -1;

	ret = open_cpu_files(instance, cpus, cpu_size, &all_cpus, &count);
	if (ret < 0)
		goto out;
	ret = read_cpu_pages(tep, instance, all_cpus, count,
			     callback, callback_context,
			     keep_going);

out:
	if (all_cpus) {
		for (i = 0; i < count; i++) {
			kbuffer_free(all_cpus[i].kbuf);
			tracefs_cpu_close(all_cpus[i].tcpu);
			free(all_cpus[i].page);
		}
		free(all_cpus);
	}

	return ret;
}

/**
 * tracefs_iterate_stop - stop the iteration over the raw events.
 * @instance: ftrace instance, can be NULL for top tracing instance.
 */
void tracefs_iterate_stop(struct tracefs_instance *instance)
{
	if (instance)
		instance->iterate_keep_going = false;
	else
		top_iterate_keep_going = false;
}

static int add_list_string(char ***list, const char *name)
{
	char **tmp;

	tmp = tracefs_list_add(*list, name);
	if (!tmp) {
		tracefs_list_free(*list);
		*list = NULL;
		return -1;
	}

	*list = tmp;
	return 0;
}

__hidden char *trace_append_file(const char *dir, const char *name)
{
	char *file;
	int ret;

	ret = asprintf(&file, "%s/%s", dir, name);

	return ret < 0 ? NULL : file;
}

static int event_file(char **path, const char *system,
		      const char *event, const char *file)
{
	if (!system || !event || !file)
		return -1;

	return asprintf(path, "events/%s/%s/%s",
			system, event, file);
}

/**
 * tracefs_event_get_file - return a file in an event directory
 * @instance: The instance the event is in (NULL for top level)
 * @system: The system name that the event file is in
 * @event: The event name of the event
 * @file: The name of the file in the event directory.
 *
 * Returns a path to a file in the event director.
 * or NULL on error. The path returned must be freed with
 * tracefs_put_tracing_file().
 */
char *tracefs_event_get_file(struct tracefs_instance *instance,
			     const char *system, const char *event,
			     const char *file)
{
	char *instance_path;
	char *path;
	int ret;

	ret = event_file(&path, system, event, file);
	if (ret < 0)
		return NULL;

	instance_path = tracefs_instance_get_file(instance, path);
	free(path);

	return instance_path;
}

/**
 * tracefs_event_file_read - read the content from an event file
 * @instance: The instance the event is in (NULL for top level)
 * @system: The system name that the event file is in
 * @event: The event name of the event
 * @file: The name of the file in the event directory.
 * @psize: the size of the content read.
 *
 * Reads the content of the event file that is passed via the
 * arguments and returns the content.
 *
 * Return a string containing the content of the file or NULL
 * on error. The string returned must be freed with free().
 */
char *tracefs_event_file_read(struct tracefs_instance *instance,
			      const char *system, const char *event,
			      const char *file, int *psize)
{
	char *content;
	char *path;
	int ret;

	ret = event_file(&path, system, event, file);
	if (ret < 0)
		return NULL;

	content = tracefs_instance_file_read(instance, path, psize);
	free(path);
	return content;
}

/**
 * tracefs_event_file_write - write to an event file
 * @instance: The instance the event is in (NULL for top level)
 * @system: The system name that the event file is in
 * @event: The event name of the event
 * @file: The name of the file in the event directory.
 * @str: The string to write into the file
 *
 * Writes the content of @str to a file in the instance directory.
 * The content of the file will be overwritten by @str.
 *
 * Return 0 on success, and -1 on error.
 */
int tracefs_event_file_write(struct tracefs_instance *instance,
			     const char *system, const char *event,
			     const char *file, const char *str)
{
	char *path;
	int ret;

	ret = event_file(&path, system, event, file);
	if (ret < 0)
		return -1;

	ret = tracefs_instance_file_write(instance, path, str);
	free(path);
	return ret;
}

/**
 * tracefs_event_file_append - write to an event file
 * @instance: The instance the event is in (NULL for top level)
 * @system: The system name that the event file is in
 * @event: The event name of the event
 * @file: The name of the file in the event directory.
 * @str: The string to write into the file
 *
 * Writes the content of @str to a file in the instance directory.
 * The content of @str will be appended to the content of the file.
 * The current content should not be lost.
 *
 * Return 0 on success, and -1 on error.
 */
int tracefs_event_file_append(struct tracefs_instance *instance,
			      const char *system, const char *event,
			      const char *file, const char *str)
{
	char *path;
	int ret;

	ret = event_file(&path, system, event, file);
	if (ret < 0)
		return -1;

	ret = tracefs_instance_file_append(instance, path, str);
	free(path);
	return ret;
}

/**
 * tracefs_event_file_clear - clear an event file
 * @instance: The instance the event is in (NULL for top level)
 * @system: The system name that the event file is in
 * @event: The event name of the event
 * @file: The name of the file in the event directory.
 *
 * Clears the content of the event file. That is, it is opened
 * with O_TRUNC and then closed.
 *
 * Return 0 on success, and -1 on error.
 */
int tracefs_event_file_clear(struct tracefs_instance *instance,
			     const char *system, const char *event,
			     const char *file)
{
	char *path;
	int ret;

	ret = event_file(&path, system, event, file);
	if (ret < 0)
		return -1;

	ret = tracefs_instance_file_clear(instance, path);
	free(path);
	return ret;
}

/**
 * tracefs_event_file_exits - test if a file exists
 * @instance: The instance the event is in (NULL for top level)
 * @system: The system name that the event file is in
 * @event: The event name of the event
 * @file: The name of the file in the event directory.
 *
 * Return true if the file exists, false if it odes not or
 * an error occurred.
 */
bool tracefs_event_file_exists(struct tracefs_instance *instance,
			       const char *system, const char *event,
			       const char *file)
{
	char *path;
	bool ret;

	if (event_file(&path, system, event, file) < 0)
		return false;

	ret = tracefs_file_exists(instance, path);
	free(path);
	return ret;
}

/**
 * tracefs_event_systems - return list of systems for tracing
 * @tracing_dir: directory holding the "events" directory
 *		 if NULL, top tracing directory is used
 *
 * Returns an allocated list of system names. Both the names and
 * the list must be freed with tracefs_list_free()
 * The list returned ends with a "NULL" pointer
 */
char **tracefs_event_systems(const char *tracing_dir)
{
	struct dirent *dent;
	char **systems = NULL;
	char *events_dir;
	struct stat st;
	DIR *dir;
	int ret;

	if (!tracing_dir)
		tracing_dir = tracefs_tracing_dir();

	if (!tracing_dir)
		return NULL;

	events_dir = trace_append_file(tracing_dir, "events");
	if (!events_dir)
		return NULL;

	/*
	 * Search all the directories in the events directory,
	 * and collect the ones that have the "enable" file.
	 */
	ret = stat(events_dir, &st);
	if (ret < 0 || !S_ISDIR(st.st_mode))
		goto out_free;

	dir = opendir(events_dir);
	if (!dir)
		goto out_free;

	while ((dent = readdir(dir))) {
		const char *name = dent->d_name;
		char *enable;
		char *sys;

		if (strcmp(name, ".") == 0 ||
		    strcmp(name, "..") == 0)
			continue;

		sys = trace_append_file(events_dir, name);
		ret = stat(sys, &st);
		if (ret < 0 || !S_ISDIR(st.st_mode)) {
			free(sys);
			continue;
		}

		enable = trace_append_file(sys, "enable");

		ret = stat(enable, &st);
		if (ret >= 0) {
			if (add_list_string(&systems, name) < 0)
				goto out_free;
		}
		free(enable);
		free(sys);
	}

	closedir(dir);

 out_free:
	free(events_dir);
	return systems;
}

/**
 * tracefs_system_events - return list of events for system
 * @tracing_dir: directory holding the "events" directory
 * @system: the system to return the events for
 *
 * Returns an allocated list of event names. Both the names and
 * the list must be freed with tracefs_list_free()
 * The list returned ends with a "NULL" pointer
 */
char **tracefs_system_events(const char *tracing_dir, const char *system)
{
	struct dirent *dent;
	char **events = NULL;
	char *system_dir = NULL;
	struct stat st;
	DIR *dir;
	int ret;

	if (!tracing_dir)
		tracing_dir = tracefs_tracing_dir();

	if (!tracing_dir || !system)
		return NULL;

	asprintf(&system_dir, "%s/events/%s", tracing_dir, system);
	if (!system_dir)
		return NULL;

	ret = stat(system_dir, &st);
	if (ret < 0 || !S_ISDIR(st.st_mode))
		goto out_free;

	dir = opendir(system_dir);
	if (!dir)
		goto out_free;

	while ((dent = readdir(dir))) {
		const char *name = dent->d_name;
		char *event;

		if (strcmp(name, ".") == 0 ||
		    strcmp(name, "..") == 0)
			continue;

		event = trace_append_file(system_dir, name);
		ret = stat(event, &st);
		if (ret < 0 || !S_ISDIR(st.st_mode)) {
			free(event);
			continue;
		}

		if (add_list_string(&events, name) < 0)
			goto out_free;

		free(event);
	}

	closedir(dir);

 out_free:
	free(system_dir);

	return events;
}

static char **list_tracers(const char *tracing_dir)
{
	char *available_tracers;
	struct stat st;
	char **plugins = NULL;
	char *buf;
	char *str, *saveptr;
	char *plugin;
	int slen;
	int len;
	int ret;

	if (!tracing_dir)
		tracing_dir = tracefs_tracing_dir();

	if (!tracing_dir)
		return NULL;

	available_tracers = trace_append_file(tracing_dir, "available_tracers");
	if (!available_tracers)
		return NULL;

	ret = stat(available_tracers, &st);
	if (ret < 0)
		goto out_free;

	len = str_read_file(available_tracers, &buf, true);
	if (len <= 0)
		goto out_free;

	for (str = buf; ; str = NULL) {
		plugin = strtok_r(str, " ", &saveptr);
		if (!plugin)
			break;
		slen = strlen(plugin);
		if (!slen)
			continue;

		/* chop off any newlines */
		if (plugin[slen - 1] == '\n')
			plugin[slen - 1] = '\0';

		/* Skip the non tracers */
		if (strcmp(plugin, "nop") == 0 ||
		    strcmp(plugin, "none") == 0)
			continue;

		if (add_list_string(&plugins, plugin) < 0)
			break;
	}
	free(buf);

 out_free:
	free(available_tracers);

	return plugins;
}

/**
 * tracefs_tracers - returns an array of available tracers
 * @tracing_dir: The directory that contains the tracing directory
 *
 * Returns an allocate list of plugins. The array ends with NULL
 * Both the plugin names and array must be freed with tracefs_list_free()
 */
char **tracefs_tracers(const char *tracing_dir)
{
	return list_tracers(tracing_dir);
}

/**
 * tracefs_instance_tracers - returns an array of available tracers for an instance
 * @instance: ftrace instance, can be NULL for the top instance
 *
 * Returns an allocate list of plugins. The array ends with NULL
 * Both the plugin names and array must be freed with tracefs_list_free()
 */
char **tracefs_instance_tracers(struct tracefs_instance *instance)
{
	const char *tracing_dir = NULL;

	if (instance)
		tracing_dir = instance->trace_dir;

	return list_tracers(tracing_dir);
}

static int load_events(struct tep_handle *tep,
		       const char *tracing_dir, const char *system, bool check)
{
	int ret = 0, failure = 0;
	char **events = NULL;
	struct stat st;
	int len = 0;
	int i;

	if (!tracing_dir)
		tracing_dir = tracefs_tracing_dir();

	events = tracefs_system_events(tracing_dir, system);
	if (!events)
		return -ENOENT;

	for (i = 0; events[i]; i++) {
		char *format;
		char *buf;

		ret = asprintf(&format, "%s/events/%s/%s/format",
			       tracing_dir, system, events[i]);
		if (ret < 0) {
			failure = -ENOMEM;
			break;
		}

		ret = stat(format, &st);
		if (ret < 0)
			goto next_event;

		/* check if event is already added, to avoid duplicates */
		if (check && tep_find_event_by_name(tep, system, events[i]))
			goto next_event;

		len = str_read_file(format, &buf, true);
		if (len <= 0)
			goto next_event;

		ret = tep_parse_event(tep, buf, len, system);
		free(buf);
next_event:
		free(format);
		if (ret)
			failure = ret;
	}

	tracefs_list_free(events);
	return failure;
}

__hidden int trace_rescan_events(struct tep_handle *tep,
				const char *tracing_dir, const char *system)
{
	/* ToDo: add here logic for deleting removed events from tep handle */
	return load_events(tep, tracing_dir, system, true);
}

__hidden int trace_load_events(struct tep_handle *tep,
			       const char *tracing_dir, const char *system)
{
	return load_events(tep, tracing_dir, system, false);
}

__hidden struct tep_event *get_tep_event(struct tep_handle *tep,
					 const char *system, const char *name)
{
	struct tep_event *event;

	/* Check if event exists in the system */
	if (!tracefs_event_file_exists(NULL, system, name, "format"))
		return NULL;

	/* If the event is already loaded in the tep, return it */
	event = tep_find_event_by_name(tep, system, name);
	if (event)
		return event;

	/* Try to load any new events from the given system */
	if (trace_rescan_events(tep, NULL, system))
		return NULL;

	return tep_find_event_by_name(tep, system, name);
}

static int read_header(struct tep_handle *tep, const char *tracing_dir)
{
	struct stat st;
	char *header;
	char *buf;
	int len;
	int ret = -1;

	header = trace_append_file(tracing_dir, "events/header_page");

	ret = stat(header, &st);
	if (ret < 0)
		goto out;

	len = str_read_file(header, &buf, true);
	if (len <= 0)
		goto out;

	tep_parse_header_page(tep, buf, len, sizeof(long));

	free(buf);

	ret = 0;
 out:
	free(header);
	return ret;
}

static bool contains(const char *name, const char * const *names)
{
	if (!names)
		return false;
	for (; *names; names++)
		if (strcmp(name, *names) == 0)
			return true;
	return false;
}

static void load_kallsyms(struct tep_handle *tep)
{
	char *buf;

	if (str_read_file("/proc/kallsyms", &buf, false) <= 0)
		return;

	tep_parse_kallsyms(tep, buf);
	free(buf);
}

static int load_saved_cmdlines(const char *tracing_dir,
			       struct tep_handle *tep, bool warn)
{
	char *path;
	char *buf;
	int ret;

	path = trace_append_file(tracing_dir, "saved_cmdlines");
	if (!path)
		return -1;

	ret = str_read_file(path, &buf, false);
	free(path);
	if (ret <= 0)
		return -1;

	ret = tep_parse_saved_cmdlines(tep, buf);
	free(buf);

	return ret;
}

static void load_printk_formats(const char *tracing_dir,
				struct tep_handle *tep)
{
	char *path;
	char *buf;
	int ret;

	path = trace_append_file(tracing_dir, "printk_formats");
	if (!path)
		return;

	ret = str_read_file(path, &buf, false);
	free(path);
	if (ret <= 0)
		return;

	tep_parse_printk_formats(tep, buf);
	free(buf);
}

/*
 * Do a best effort attempt to load kallsyms, saved_cmdlines and
 * printk_formats. If they can not be loaded, then this will not
 * do the mappings. But this does not fail the loading of events.
 */
static void load_mappings(const char *tracing_dir,
			  struct tep_handle *tep)
{
	load_kallsyms(tep);

	/* If there's no tracing_dir no reason to go further */
	if (!tracing_dir)
		tracing_dir = tracefs_tracing_dir();

	if (!tracing_dir)
		return;

	load_saved_cmdlines(tracing_dir, tep, false);
	load_printk_formats(tracing_dir, tep);
}

int tracefs_load_cmdlines(const char *tracing_dir, struct tep_handle *tep)
{

	if (!tracing_dir)
		tracing_dir = tracefs_tracing_dir();

	if (!tracing_dir)
		return -1;

	return load_saved_cmdlines(tracing_dir, tep, true);
}

static int fill_local_events_system(const char *tracing_dir,
				    struct tep_handle *tep,
				    const char * const *sys_names,
				    int *parsing_failures)
{
	char **systems = NULL;
	int ret;
	int i;

	if (!tracing_dir)
		tracing_dir = tracefs_tracing_dir();
	if (!tracing_dir)
		return -1;

	systems = tracefs_event_systems(tracing_dir);
	if (!systems)
		return -1;

	ret = read_header(tep, tracing_dir);
	if (ret < 0) {
		ret = -1;
		goto out;
	}

	if (parsing_failures)
		*parsing_failures = 0;

	for (i = 0; systems[i]; i++) {
		if (sys_names && !contains(systems[i], sys_names))
			continue;
		ret = trace_load_events(tep, tracing_dir, systems[i]);
		if (ret && parsing_failures)
			(*parsing_failures)++;
	}

	/* Include ftrace, as it is excluded for not having "enable" file */
	if (!sys_names || contains("ftrace", sys_names))
		trace_load_events(tep, tracing_dir, "ftrace");

	load_mappings(tracing_dir, tep);

	/* always succeed because parsing failures are not critical */
	ret = 0;
out:
	tracefs_list_free(systems);
	return ret;
}

static void set_tep_cpus(const char *tracing_dir, struct tep_handle *tep)
{
	struct stat st;
	char path[PATH_MAX];
	int cpus = sysconf(_SC_NPROCESSORS_CONF);
	int max_cpu = 0;
	int ret;
	int i;

	if (!tracing_dir)
		tracing_dir = tracefs_tracing_dir();

	/*
	 * Paranoid: in case sysconf() above does not work.
	 * And we also only care about the number of tracing
	 * buffers that exist. If cpus is 32, but the top half
	 * is offline, there may only be 16 tracing buffers.
	 * That's what we want to know.
	 */
	for (i = 0; !cpus || i < cpus; i++) {
		snprintf(path, PATH_MAX, "%s/per_cpu/cpu%d", tracing_dir, i);
		ret = stat(path, &st);
		if (!ret && S_ISDIR(st.st_mode))
			max_cpu = i + 1;
		else if (i >= cpus)
			break;
	}

	if (!max_cpu)
		max_cpu = cpus;

	tep_set_cpus(tep, max_cpu);
}

/**
 * tracefs_local_events_system - create a tep from the events of the specified subsystem.
 *
 * @tracing_dir: The directory that contains the events.
 * @sys_name: Array of system names, to load the events from.
 * The last element from the array must be NULL
 *
 * Returns a tep structure that contains the tep local to
 * the system.
 */
struct tep_handle *tracefs_local_events_system(const char *tracing_dir,
					       const char * const *sys_names)
{
	struct tep_handle *tep = NULL;

	tep = tep_alloc();
	if (!tep)
		return NULL;

	if (fill_local_events_system(tracing_dir, tep, sys_names, NULL)) {
		tep_free(tep);
		tep = NULL;
	}

	set_tep_cpus(tracing_dir, tep);

	/* Set the long size for this tep handle */
	tep_set_long_size(tep, tep_get_header_page_size(tep));

	return tep;
}

/**
 * tracefs_local_events - create a tep from the events on system
 * @tracing_dir: The directory that contains the events.
 *
 * Returns a tep structure that contains the teps local to
 * the system.
 */
struct tep_handle *tracefs_local_events(const char *tracing_dir)
{
	return tracefs_local_events_system(tracing_dir, NULL);
}

/**
 * tracefs_fill_local_events - Fill a tep with the events on system
 * @tracing_dir: The directory that contains the events.
 * @tep: Allocated tep handler which will be filled
 * @parsing_failures: return number of failures while parsing the event files
 *
 * Returns whether the operation succeeded
 */
int tracefs_fill_local_events(const char *tracing_dir,
			       struct tep_handle *tep, int *parsing_failures)
{
	return fill_local_events_system(tracing_dir, tep,
					NULL, parsing_failures);
}

static bool match(const char *str, regex_t *re)
{
	return regexec(re, str, 0, NULL, 0) == 0;
}

enum event_state {
	STATE_INIT,
	STATE_ENABLED,
	STATE_DISABLED,
	STATE_MIXED,
	STATE_ERROR,
};

static int read_event_state(struct tracefs_instance *instance, const char *file,
			    enum event_state *state)
{
	char *val;
	int ret = 0;

	if (*state == STATE_ERROR)
		return -1;

	val = tracefs_instance_file_read(instance, file, NULL);
	if (!val)
		return -1;

	switch (val[0]) {
	case '0':
		switch (*state) {
		case STATE_INIT:
			*state = STATE_DISABLED;
			break;
		case STATE_ENABLED:
			*state = STATE_MIXED;
			break;
		default:
			break;
		}
		break;
	case '1':
		switch (*state) {
		case STATE_INIT:
			*state = STATE_ENABLED;
			break;
		case STATE_DISABLED:
			*state = STATE_MIXED;
			break;
		default:
			break;
		}
		break;
	case 'X':
		*state = STATE_MIXED;
		break;
	default:
		*state = TRACEFS_ERROR;
		ret = -1;
		break;
	}
	free(val);

	return ret;
}

static int enable_disable_event(struct tracefs_instance *instance,
				const char *system, const char *event,
				bool enable, enum event_state *state)
{
	const char *str = enable ? "1" : "0";
	char *system_event;
	int ret;

	ret = asprintf(&system_event, "events/%s/%s/enable", system, event);
	if (ret < 0)
		return ret;

	if (state)
		ret = read_event_state(instance, system_event, state);
	else
		ret = tracefs_instance_file_write(instance, system_event, str);
	free(system_event);

	return ret;
}

static int enable_disable_system(struct tracefs_instance *instance,
				 const char *system, bool enable,
				 enum event_state *state)
{
	const char *str = enable ? "1" : "0";
	char *system_path;
	int ret;

	ret = asprintf(&system_path, "events/%s/enable", system);
	if (ret < 0)
		return ret;

	if (state)
		ret = read_event_state(instance, system_path, state);
	else
		ret = tracefs_instance_file_write(instance, system_path, str);
	free(system_path);

	return ret;
}

static int enable_disable_all(struct tracefs_instance *instance,
			      bool enable)
{
	const char *str = enable ? "1" : "0";
	int ret;

	ret = tracefs_instance_file_write(instance, "events/enable", str);
	return ret < 0 ? ret : 0;
}

static int make_regex(regex_t *re, const char *match)
{
	int len = strlen(match);
	char str[len + 3];
	char *p = &str[0];

	if (!len || match[0] != '^')
		*(p++) = '^';

	strcpy(p, match);
	p += len;

	if (!len || match[len-1] != '$')
		*(p++) = '$';

	*p = '\0';

	return regcomp(re, str, REG_ICASE|REG_NOSUB);
}

static int event_enable_disable(struct tracefs_instance *instance,
				const char *system, const char *event,
				bool enable, enum event_state *state)
{
	regex_t system_re, event_re;
	char **systems;
	char **events = NULL;
	int ret = -1;
	int s, e;

	/* Handle all events first */
	if (!system && !event)
		return enable_disable_all(instance, enable);

	systems = tracefs_event_systems(NULL);
	if (!systems)
		goto out_free;

	if (system) {
		ret = make_regex(&system_re, system);
		if (ret < 0)
			goto out_free;
	}
	if (event) {
		ret = make_regex(&event_re, event);
		if (ret < 0) {
			if (system)
				regfree(&system_re);
			goto out_free;
		}
	}

	ret = -1;
	for (s = 0; systems[s]; s++) {
		if (system && !match(systems[s], &system_re))
			continue;

		/* Check for the short cut first */
		if (!event) {
			ret = enable_disable_system(instance, systems[s], enable, state);
			if (ret < 0)
				break;
			ret = 0;
			continue;
		}

		events = tracefs_system_events(NULL, systems[s]);
		if (!events)
			continue; /* Error? */

		for (e = 0; events[e]; e++) {
			if (!match(events[e], &event_re))
				continue;
			ret = enable_disable_event(instance, systems[s],
						   events[e], enable, state);
			if (ret < 0)
				break;
			ret = 0;
		}
		tracefs_list_free(events);
		events = NULL;
	}
	if (system)
		regfree(&system_re);
	if (event)
		regfree(&event_re);

 out_free:
	tracefs_list_free(systems);
	tracefs_list_free(events);
	return ret;
}

/**
 * tracefs_event_enable - enable specified events
 * @instance: ftrace instance, can be NULL for the top instance
 * @system: A regex of a system (NULL to match all systems)
 * @event: A regex of the event in the system (NULL to match all events)
 *
 * This will enable events that match the @system and @event.
 * If both @system and @event are NULL, then it will enable all events.
 * If @system is NULL, it will look at all systems for matching events
 * to @event.
 * If @event is NULL, then it will enable all events in the systems
 * that match @system.
 *
 * Returns 0 on success, and -1 if it encountered an error,
 * or if no events matched. If no events matched, then -1 is set
 * but errno will not be.
 */
int tracefs_event_enable(struct tracefs_instance *instance,
			 const char *system, const char *event)
{
	return event_enable_disable(instance, system, event, true, NULL);
}

int tracefs_event_disable(struct tracefs_instance *instance,
			  const char *system, const char *event)
{
	return event_enable_disable(instance, system, event, false, NULL);
}

/**
 * tracefs_event_is_enabled - return if the event is enabled or not
 * @instance: ftrace instance, can be NULL for the top instance
 * @system: The name of the system to check
 * @event: The name of the event to check
 *
 * Checks is an event or multiple events are enabled.
 *
 * If @system is NULL, then it will check all the systems where @event is
 * a match.
 *
 * If @event is NULL, then it will check all events where @system is a match.
 *
 * If both @system and @event are NULL, then it will check all events
 *
 * Returns TRACEFS_ALL_ENABLED if all matching are enabled.
 * Returns TRACEFS_SOME_ENABLED if some are enabled and some are not
 * Returns TRACEFS_ALL_DISABLED if none of the events are enabled.
 * Returns TRACEFS_ERROR if there is an error reading the events.
 */
enum tracefs_enable_state
tracefs_event_is_enabled(struct tracefs_instance *instance,
			 const char *system, const char *event)
{
	enum event_state state = STATE_INIT;
	int ret;

	ret = event_enable_disable(instance, system, event, false, &state);

	if (ret < 0)
		return TRACEFS_ERROR;

	switch (state) {
	case STATE_ENABLED:
		return TRACEFS_ALL_ENABLED;
	case STATE_DISABLED:
		return TRACEFS_ALL_DISABLED;
	case STATE_MIXED:
		return TRACEFS_SOME_ENABLED;
	default:
		return TRACEFS_ERROR;
	}
}
