// SPDX-License-Identifier: LGPL-2.1
/*
 * Copyright (C) 2021 VMware Inc, Steven Rostedt <rostedt@goodmis.org>
 *
 * Updates:
 * Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com>
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>

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

#define DYNEVENTS_EVENTS "dynamic_events"
#define KPROBE_EVENTS "kprobe_events"
#define UPROBE_EVENTS "uprobe_events"
#define SYNTH_EVENTS "synthetic_events"
#define DYNEVENTS_DEFAULT_GROUP "dynamic"

#define EVENT_INDEX(B)	(ffs(B) - 1)

struct dyn_events_desc;
static int dyn_generic_parse(struct dyn_events_desc *,
			     const char *, char *, struct tracefs_dynevent **);
static int dyn_synth_parse(struct dyn_events_desc *,
			   const char *, char *, struct tracefs_dynevent **);
static int dyn_generic_del(struct dyn_events_desc *, struct tracefs_dynevent *);
static int dyn_synth_del(struct dyn_events_desc *, struct tracefs_dynevent *);

struct dyn_events_desc {
	enum tracefs_dynevent_type	type;
	const char			*file;
	const char			*prefix;
	int (*del)(struct dyn_events_desc *desc, struct tracefs_dynevent *dyn);
	int (*parse)(struct dyn_events_desc *desc, const char *group,
				char *line, struct tracefs_dynevent **ret_dyn);
} dynevents[] = {
	{TRACEFS_DYNEVENT_KPROBE,    KPROBE_EVENTS, "p", dyn_generic_del, dyn_generic_parse},
	{TRACEFS_DYNEVENT_KRETPROBE, KPROBE_EVENTS, "r", dyn_generic_del, dyn_generic_parse},
	{TRACEFS_DYNEVENT_UPROBE,    UPROBE_EVENTS, "p", dyn_generic_del, dyn_generic_parse},
	{TRACEFS_DYNEVENT_URETPROBE, UPROBE_EVENTS, "r", dyn_generic_del, dyn_generic_parse},
	{TRACEFS_DYNEVENT_EPROBE,    "",            "e", dyn_generic_del, dyn_generic_parse},
	{TRACEFS_DYNEVENT_SYNTH,     SYNTH_EVENTS, "", dyn_synth_del, dyn_synth_parse},
};

static int dyn_generic_del(struct dyn_events_desc *desc, struct tracefs_dynevent *dyn)
{
	char *str;
	int ret;

	if (dyn->system)
		ret = asprintf(&str, "-:%s/%s", dyn->system, dyn->event);
	else
		ret = asprintf(&str, "-:%s", dyn->event);

	if (ret < 0)
		return -1;

	ret = tracefs_instance_file_append(NULL, desc->file, str);
	free(str);

	return ret < 0 ? ret : 0;
}

/**
 * tracefs_dynevent_free - Free a dynamic event context
 * @devent: Pointer to a dynamic event context
 *
 * The dynamic event, described by this context, is not
 * removed from the system by this API. It only frees the memory.
 */
void tracefs_dynevent_free(struct tracefs_dynevent *devent)
{
	if (!devent)
		return;
	free(devent->system);
	free(devent->event);
	free(devent->address);
	free(devent->format);
	free(devent->prefix);
	free(devent->trace_file);
	free(devent);
}

static void parse_prefix(char *word, char **prefix, char **system, char **name)
{
	char *sav;

	*prefix = NULL;
	*system = NULL;
	*name = NULL;

	*prefix = strtok_r(word, ":", &sav);
	*system = strtok_r(NULL, "/", &sav);
	if (!(*system))
		return;

	*name = strtok_r(NULL, " \t", &sav);
	if (!(*name)) {
		*name = *system;
		*system = NULL;
	}
}

/*
 * Parse lines from dynamic_events, kprobe_events and uprobe_events files
 * PREFIX[:[SYSTEM/]EVENT] [ADDRSS] [FORMAT]
 */
static int dyn_generic_parse(struct dyn_events_desc *desc, const char *group,
			     char *line, struct tracefs_dynevent **ret_dyn)
{
	struct tracefs_dynevent *dyn;
	char *word;
	char *format = NULL;
	char *address = NULL;
	char *system;
	char *prefix;
	char *event;
	char *sav;

	if (strncmp(line, desc->prefix, strlen(desc->prefix)))
		return -1;

	word = strtok_r(line, " \t", &sav);
	if (!word || *word == '\0')
		return -1;

	parse_prefix(word, &prefix, &system, &event);
	if (!prefix)
		return -1;

	if (desc->type != TRACEFS_DYNEVENT_SYNTH) {
		address = strtok_r(NULL, " \t", &sav);
		if (!address || *address == '\0')
			return -1;
	}

	format = strtok_r(NULL, "", &sav);

	/* KPROBEs and UPROBEs share the same prefix, check the format */
	if (desc->type & (TRACEFS_DYNEVENT_UPROBE | TRACEFS_DYNEVENT_URETPROBE)) {
		if (!strchr(address, '/'))
			return -1;
	}

	if (group && (!system || strcmp(group, system) != 0))
		return -1;

	if (!ret_dyn)
		return 0;

	dyn = calloc(1, sizeof(*dyn));
	if (!dyn)
		return -1;

	dyn->type = desc->type;
	dyn->trace_file = strdup(desc->file);
	if (!dyn->trace_file)
		goto error;

	dyn->prefix = strdup(prefix);
	if (!dyn->prefix)
		goto error;

	if (system) {
		dyn->system = strdup(system);
		if (!dyn->system)
			goto error;
	}

	if (event) {
		dyn->event = strdup(event);
		if (!dyn->event)
			goto error;
	}

	if (address) {
		dyn->address = strdup(address);
		if (!dyn->address)
			goto error;
	}

	if (format) {
		dyn->format = strdup(format);
		if (!dyn->format)
			goto error;
	}

	*ret_dyn = dyn;
	return 0;
error:
	tracefs_dynevent_free(dyn);
	return -1;
}

static int dyn_synth_del(struct dyn_events_desc *desc, struct tracefs_dynevent *dyn)
{
	char *str;
	int ret;

	if (!strcmp(desc->file, DYNEVENTS_EVENTS))
		return dyn_generic_del(desc, dyn);

	ret = asprintf(&str, "!%s", dyn->event);
	if (ret < 0)
		return -1;

	ret = tracefs_instance_file_append(NULL, desc->file, str);
	free(str);

	return ret < 0 ? ret : 0;
}

/*
 * Parse lines from synthetic_events file
 * EVENT ARG [ARG]
 */
static int dyn_synth_parse(struct dyn_events_desc *desc, const char *group,
			   char *line, struct tracefs_dynevent **ret_dyn)
{
	struct tracefs_dynevent *dyn;
	char *format;
	char *event;
	char *sav;

	if (!strcmp(desc->file, DYNEVENTS_EVENTS))
		return dyn_generic_parse(desc, group, line, ret_dyn);

	/* synthetic_events file has slightly different syntax */
	event = strtok_r(line, " \t", &sav);
	if (!event || *event == '\0')
		return -1;

	format = strtok_r(NULL, "", &sav);
	if (!format || *format == '\0')
		return -1;

	if (!ret_dyn)
		return 0;

	dyn = calloc(1, sizeof(*dyn));
	if (!dyn)
		return -1;

	dyn->type = desc->type;
	dyn->trace_file = strdup(desc->file);
	if (!dyn->trace_file)
		goto error;

	dyn->event = strdup(event);
	if (!dyn->event)
		goto error;

	dyn->format = strdup(format+1);
	if (!dyn->format)
		goto error;

	*ret_dyn = dyn;
	return 0;
error:
	tracefs_dynevent_free(dyn);
	return -1;
}

static void init_devent_desc(void)
{
	int i;

	BUILD_BUG_ON(ARRAY_SIZE(dynevents) != EVENT_INDEX(TRACEFS_DYNEVENT_MAX));

	if (!tracefs_file_exists(NULL, DYNEVENTS_EVENTS))
		return;

	/* Use  ftrace dynamic_events, if available */
	for (i = 0; i < EVENT_INDEX(TRACEFS_DYNEVENT_MAX); i++) {
		/* kprobes and uprobes do not use default file */
		if (dynevents[i].prefix[0] == 'p' ||
		    dynevents[i].prefix[0] == 'r')
			continue;
		dynevents[i].file = DYNEVENTS_EVENTS;
	}

	dynevents[EVENT_INDEX(TRACEFS_DYNEVENT_SYNTH)].prefix = "s";
}

static struct dyn_events_desc *get_devent_desc(enum tracefs_dynevent_type type)
{

	static bool init;

	if (type >= TRACEFS_DYNEVENT_MAX)
		return NULL;

	if (!init) {
		init_devent_desc();
		init = true;
	}

	return &dynevents[EVENT_INDEX(type)];
}

/**
 * tfs_dynevent_alloc - Allocate new dynamic event
 * @type: Type of the dynamic event
 * @system: The system name (NULL for the default dynamic)
 * @event: Name of the event
 * @addr: The function and offset (or address) to insert the probe
 * @format: The format string to define the probe.
 *
 * Allocate a dynamic event context that will be in the @system group
 * (or dynamic if @system is NULL). Have the name of @event and
 * will be associated to @addr, if applicable for that event type
 * (function name, with or without offset, or a address). And the @format will
 * define the format of the kprobe.
 * The dynamic event is not created in the system.
 *
 * Return a pointer to a dynamic event context on success, or NULL on error.
 * The returned pointer must be freed with tracefs_dynevent_free()
 *
 * errno will be set to EINVAL if event is NULL.
 */
__hidden struct tracefs_dynevent *
tfs_dynevent_alloc(enum tracefs_dynevent_type type, const char *system,
		   const char *event, const char *address, const char *format)
{
	struct tracefs_dynevent *devent;
	struct dyn_events_desc *desc;

	if (!event) {
		errno = EINVAL;
		return NULL;
	}

	desc = get_devent_desc(type);
	if (!desc || !desc->file) {
		errno = ENOTSUP;
		return NULL;
	}

	devent = calloc(1, sizeof(*devent));
	if (!devent)
		return NULL;

	devent->type = type;
	devent->trace_file = strdup(desc->file);
	if (!devent->trace_file)
		goto err;

	if (!system)
		system = DYNEVENTS_DEFAULT_GROUP;
	devent->system = strdup(system);
	if (!devent->system)
		goto err;

	devent->event = strdup(event);
	if (!devent->event)
		goto err;

	devent->prefix = strdup(desc->prefix);
	if (!devent->prefix)
		goto err;

	if (address) {
		devent->address = strdup(address);
		if (!devent->address)
			goto err;
	}
	if (format) {
		devent->format = strdup(format);
		if (!devent->format)
			goto err;
	}

	return devent;
err:
	tracefs_dynevent_free(devent);
	return NULL;
}

/**
 * tracefs_dynevent_create - Create a dynamic event in the system
 * @devent: Pointer to a dynamic event context, describing the event
 *
 * Return 0 on success, or -1 on error.
 */
int tracefs_dynevent_create(struct tracefs_dynevent *devent)
{
	char *str;
	int ret;

	if (!devent)
		return -1;

	if (devent->system && devent->system[0])
		ret = asprintf(&str, "%s%s%s/%s %s %s\n",
				devent->prefix, strlen(devent->prefix) ? ":" : "",
				devent->system, devent->event,
				devent->address ? devent->address : "",
				devent->format ? devent->format : "");
	else
		ret = asprintf(&str, "%s%s%s %s %s\n",
				devent->prefix, strlen(devent->prefix) ? ":" : "",
				devent->event,
				devent->address ? devent->address : "",
				devent->format ? devent->format : "");
	if (ret < 0)
		return -1;

	ret = tracefs_instance_file_append(NULL, devent->trace_file, str);
	free(str);

	return ret < 0 ? ret : 0;
}

static void disable_events(const char *system, const char *event,
			   char **list)
{
	struct tracefs_instance *instance;
	int i;

	/*
	 * Note, this will not fail even on error.
	 * That is because even if something fails, it may still
	 * work enough to clear the kprobes. If that's the case
	 * the clearing after the loop will succeed and the function
	 * is a success, even though other parts had failed. If
	 * one of the kprobe events is enabled in one of the
	 * instances that fail, then the clearing will fail too
	 * and the function will return an error.
	 */

	tracefs_event_disable(NULL, system, event);
	/* No need to test results */

	if (!list)
		return;

	for (i = 0; list[i]; i++) {
		instance = tracefs_instance_alloc(NULL, list[i]);
		/* If this fails, try the next one */
		if (!instance)
			continue;
		tracefs_event_disable(instance, system, event);
		tracefs_instance_free(instance);
	}
}

/**
 * tracefs_dynevent_destroy - Remove a dynamic event from the system
 * @devent: A dynamic event context, describing the dynamic event that will be deleted.
 * @force: Will attempt to disable all events before removing them.
 *
 * The dynamic event context is not freed by this API. It only removes the event from the system.
 * If there are any enabled events, and @force is not set, then it will error with -1 and errno
 * to be EBUSY.
 *
 * Return 0 on success, or -1 on error.
 */
int tracefs_dynevent_destroy(struct tracefs_dynevent *devent, bool force)
{
	struct dyn_events_desc *desc;
	char **instance_list;

	if (!devent)
		return -1;

	if (force) {
		instance_list = tracefs_instances(NULL);
		disable_events(devent->system, devent->event, instance_list);
		tracefs_list_free(instance_list);
	}

	desc = get_devent_desc(devent->type);
	if (!desc)
		return -1;

	return desc->del(desc, devent);
}

static int get_dynevent(enum tracefs_dynevent_type type, const char *system,
			struct tracefs_dynevent ***ret_all, int count)
{
	struct dyn_events_desc *desc;
	struct tracefs_dynevent *devent, **tmp, **all;
	char *content;
	char *line;
	char *next;
	int ret = -1;

	desc = get_devent_desc(type);
	if (!desc)
		return -1;

	if (!tracefs_file_exists(NULL, desc->file))
		return -1;

	content = tracefs_instance_file_read(NULL, desc->file, NULL);
	/* File exists, but may be empty */
	if (!content)
		return 0;

	if (ret_all)
		all = *ret_all;

	line = content;
	do {
		next = strchr(line, '\n');
		if (next)
			*next = '\0';
		ret = desc->parse(desc, system, line, ret_all ? &devent : NULL);
		if (!ret) {
			if (ret_all) {
				tmp = realloc(all, (count + 2) * sizeof(*tmp));
				if (!tmp)
					break;
				all = tmp;
				all[count] = devent;
				all[count + 1] = NULL;
			}
			count++;
		}
		line = next + 1;
	} while (next);

	free(content);
	if (ret_all)
		*ret_all = all;

	return count;
}

static int get_all_dynevents(enum tracefs_dynevent_type type, const char *system,
			     struct tracefs_dynevent ***ret_all)
{
	int count = 0;
	int i;

	if (ret_all)
		*ret_all = NULL;

	for (i = 0; i < EVENT_INDEX(TRACEFS_DYNEVENT_MAX); i++) {
		if (!((1 << i) & type))
			continue;

		count = get_dynevent((1 << i), system, ret_all, count);
		if (count < 0) {
			count = 0;
			break;
		}
	}

	if (!count) {
		if (ret_all) {
			free(*ret_all);
			*ret_all = NULL;
		}
		count = -1;
	}
	return count;
}

/**
 * tracefs_dynevent_list_free - Deletes an array of pointers to dynamic event contexts
 * @events: An array of pointers to dynamic event contexts. The last element of the array
 *	    must be a NULL pointer.
 */
void tracefs_dynevent_list_free(struct tracefs_dynevent **events)
{
	int i;

	if (!events)
		return;

	for (i = 0; events[i]; i++)
		tracefs_dynevent_free(events[i]);

	free(events);
}

/**
 * tracefs_dynevent_get_all - return an array of pointers to dynamic events of given types
 * @types: Dynamic event type, or bitmask of dynamic event types. If 0 is passed, all types
 *	   are considered.
 * @system: Get events from that system only. If @system is NULL, events from all systems
 *	    are returned.
 *
 * Returns an array of pointers to dynamic events of given types that exist in the system.
 * The array must be freed with tracefs_dynevent_list_free(). If there are no events a NULL
 * pointer is returned.
 */
struct tracefs_dynevent **
tracefs_dynevent_get_all(unsigned int types, const char *system)
{
	struct tracefs_dynevent **events;
	int count;

	count = get_all_dynevents(types, system, &events);
	if (count <= 0) {
		free(events);
		return NULL;
	}

	return events;
}

/**
 * tracefs_dynevent_get - return a single dynamic event if it exists
 * @type; Dynamic event type
 * @system: Get events from that system only. May be NULL.
 * @event: Get event of the system type (may not be NULL)
 *
 * Returns the dynamic event of the given @type and @system for with the @event
 * name. If @system is NULL, it will return the first dynamic event that it finds
 * that matches the @event name.
 *
 * The returned event must be freed with tracefs_dynevent_free().
 * NULL is returned if no event match is found, or other error.
 */
struct tracefs_dynevent *
tracefs_dynevent_get(enum tracefs_dynevent_type type, const char *system,
		     const char *event)
{
	struct tracefs_dynevent **events;
	struct tracefs_dynevent *devent = NULL;
	int count;
	int i;

	if (!event) {
		errno = -EINVAL;
		return NULL;
	}

	count = get_all_dynevents(type, system, &events);
	if (count <= 0)
		return NULL;

	for (i = 0; i < count; i++) {
		if (strcmp(events[i]->event, event) == 0)
			break;
	}
	if (i < count) {
		devent = events[i];
		events[i] = NULL;
	}

	tracefs_dynevent_list_free(events);

	return devent;
}

static int destroy_dynevents(struct tracefs_dynevent **all, bool force)
{
	int ret = 0;
	int i;

	if (!all)
		return 0;

	for (i = 0; all[i]; i++) {
		if (tracefs_dynevent_destroy(all[i], force))
			ret = -1;
	}

	return ret;
}

/**
 * tracefs_dynevent_destroy_all - removes all dynamic events of given types from the system
 * @types: Dynamic event type, or bitmask of dynamic event types. If 0 is passed, all types
 *	   are considered.
 * @force: Will attempt to disable all events before removing them.
 *
 * Will remove all dynamic events of the given types from the system. If there are any enabled
 * events, and @force is not set, then the removal of these will fail. If @force is set, then
 * it will attempt to disable all the events in all instances before removing them.
 *
 * Returns zero if all requested events are removed successfully, or -1 if some of them are not
 * removed.
 */
int tracefs_dynevent_destroy_all(unsigned int types, bool force)
{
	struct tracefs_dynevent **all;
	int ret = 0;

	/*
	 * Destroy synthetic events first, as they may depend on
	 * other dynamic events.
	 */
	if (types & TRACEFS_DYNEVENT_SYNTH) {
		all = tracefs_dynevent_get_all(TRACEFS_DYNEVENT_SYNTH, NULL);
		ret = destroy_dynevents(all, force);
		tracefs_dynevent_list_free(all);
		types &= ~TRACEFS_DYNEVENT_SYNTH;
	}

	/* Eprobes may depend on other events as well */
	if (types & TRACEFS_DYNEVENT_EPROBE) {
		all = tracefs_dynevent_get_all(TRACEFS_DYNEVENT_EPROBE, NULL);
		ret |= destroy_dynevents(all, force);
		tracefs_dynevent_list_free(all);
		types &= ~TRACEFS_DYNEVENT_EPROBE;
	}

	/* Destroy the rest */
	all = tracefs_dynevent_get_all(types, NULL);
	if (!all)
		return 0;

	ret |= destroy_dynevents(all, force);

	tracefs_dynevent_list_free(all);

	return ret;
}

/**
 * tfs_dynevent_get_count - Count dynamic events of given types and system
 * @types: Dynamic event type, or bitmask of dynamic event types. If 0 is passed, all types
 *	   are considered.
 * @system: Count events from that system only. If @system is NULL, events from all systems
 *	    are counted.
 *
 * Return the count of requested dynamic events
 */
__hidden int tfs_dynevent_get_count(unsigned int types, const char *system)
{
	int count, all = 0;
	int i;

	for (i = 1; i < TRACEFS_DYNEVENT_MAX; i <<= 1) {
		if (types) {
			if (i > types)
				break;
			if (!(types & i))
				continue;
		}
		count = get_all_dynevents(i, system, NULL);
		if (count > 0)
			all += count;
	}

	return all;
}

static enum tracefs_dynevent_type
dynevent_info(struct tracefs_dynevent *dynevent, char **system,
	      char **event, char **prefix, char **addr, char **format)
{
	char **lv[] = { system, event, prefix, addr, format };
	char **rv[] = { &dynevent->system, &dynevent->event, &dynevent->prefix,
			&dynevent->address, &dynevent->format };
	int i;

	for (i = 0; i < ARRAY_SIZE(lv); i++) {
		if (lv[i]) {
			if (*rv[i]) {
				*lv[i] = strdup(*rv[i]);
				if (!*lv[i])
					goto error;
			} else {
				*lv[i] = NULL;
			}
		}
	}

	return dynevent->type;

error:
	for (i--; i >= 0; i--) {
		if (lv[i])
			free(*lv[i]);
	}

	return TRACEFS_DYNEVENT_UNKNOWN;
}

/**
 * tracefs_dynevent_info - return details of a dynamic event
 * @dynevent: A dynamic event context, describing given dynamic event.
 * @group: return, group in which the dynamic event is configured
 * @event: return, name of the dynamic event
 * @prefix: return, prefix string of the dynamic event
 * @addr: return, the function and offset (or address) of the dynamic event
 * @format: return, the format string of the dynamic event
 *
 * Returns the type of the dynamic event, or TRACEFS_DYNEVENT_UNKNOWN in case of an error.
 * Any of the @group, @event, @prefix, @addr and @format parameters are optional.
 * If a valid pointer is passed, in case of success - a string is allocated and returned.
 * These strings must be freed with free().
 */
enum tracefs_dynevent_type
tracefs_dynevent_info(struct tracefs_dynevent *dynevent, char **system,
		      char **event, char **prefix, char **addr, char **format)
{
	if (!dynevent)
		return TRACEFS_DYNEVENT_UNKNOWN;

	return dynevent_info(dynevent, system, event, prefix, addr, format);
}

/**
 * tracefs_dynevent_get_event - return tep event representing the given dynamic event
 * @tep: a handle to the trace event parser context that holds the events
 * @dynevent: a dynamic event context, describing given dynamic event.
 *
 * Returns a pointer to a tep event describing the given dynamic event. The pointer
 * is managed by the @tep handle and must not be freed. In case of an error, or in case
 * the requested dynamic event is missing in the @tep handler - NULL is returned.
 */
struct tep_event *
tracefs_dynevent_get_event(struct tep_handle *tep, struct tracefs_dynevent *dynevent)
{
	if (!tep || !dynevent || !dynevent->event)
		return NULL;

	return tfs_get_tep_event(tep, dynevent->system, dynevent->event);
}
