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

#include <event-parse.h>
#include <event-utils.h>
#include "tracefs.h"
#include "tracefs-local.h"

#define TRACEFS_PATH "/sys/kernel/tracing"
#define DEBUGFS_PATH "/sys/kernel/debug"

#define ERROR_LOG "error_log"

#define _STR(x) #x
#define STR(x) _STR(x)

static int log_level = TEP_LOG_CRITICAL;
static char *custom_tracing_dir;

/**
 * tracefs_set_loglevel - set log level of the library
 * @level: desired level of the library messages
 */
void tracefs_set_loglevel(enum tep_loglevel level)
{
	log_level = level;
	tep_set_loglevel(level);
}

void __weak tracefs_warning(const char *fmt, ...)
{
	va_list ap;

	if (log_level < TEP_LOG_WARNING)
		return;

	va_start(ap, fmt);
	tep_vprint("libtracefs", TEP_LOG_WARNING, true, fmt, ap);
	va_end(ap);
}

static int mount_tracefs(void)
{
	struct stat st;
	int ret;

	/* make sure debugfs exists */
	ret = stat(TRACEFS_PATH, &st);
	if (ret < 0)
		return -1;

	ret = mount("nodev", TRACEFS_PATH,
		    "tracefs", 0, NULL);

	return ret;
}

static int mount_debugfs(void)
{
	struct stat st;
	int ret;

	/* make sure debugfs exists */
	ret = stat(DEBUGFS_PATH, &st);
	if (ret < 0)
		return -1;

	ret = mount("nodev", DEBUGFS_PATH,
		    "debugfs", 0, NULL);

	return ret;
}

/* Exported for testing purpose only */
__hidden char *find_tracing_dir(bool debugfs, bool mount)
{
	char *debug_str = NULL;
	char fspath[PATH_MAX+1];
	char *tracing_dir;
	char type[100];
	int use_debug = 0;
	FILE *fp;

	fp = fopen("/proc/mounts", "r");
	if (!fp) {
		tracefs_warning("Can't open /proc/mounts for read");
		return NULL;
	}

	while (fscanf(fp, "%*s %"
		      STR(PATH_MAX)
		      "s %99s %*s %*d %*d\n",
		      fspath, type) == 2) {
		if (!debugfs && strcmp(type, "tracefs") == 0)
			break;
		if (!debug_str && strcmp(type, "debugfs") == 0) {
			if (debugfs)
				break;
			debug_str = strdup(fspath);
			if (!debug_str) {
				fclose(fp);
				return NULL;
			}
		}
	}
	fclose(fp);

	if (debugfs) {
		if (strcmp(type, "debugfs") != 0) {
			if (!mount || mount_debugfs() < 0)
				return NULL;
			strcpy(fspath, DEBUGFS_PATH);
		}
	} else if (strcmp(type, "tracefs") != 0) {
		if (!mount || mount_tracefs() < 0) {
			if (debug_str) {
				strncpy(fspath, debug_str, PATH_MAX);
				fspath[PATH_MAX] = 0;
			} else {
				if (!mount || mount_debugfs() < 0) {
					if (mount)
						tracefs_warning("debugfs not mounted, please mount");
					free(debug_str);
					return NULL;
				}
				strcpy(fspath, DEBUGFS_PATH);
			}
			use_debug = 1;
		} else
			strcpy(fspath, TRACEFS_PATH);
	}
	free(debug_str);

	if (use_debug) {
		int ret;

		ret = asprintf(&tracing_dir, "%s/tracing", fspath);
		if (ret < 0)
			return NULL;
	} else {
		tracing_dir = strdup(fspath);
		if (!tracing_dir)
			return NULL;
	}

	return tracing_dir;
}

/**
 * tracefs_tracing_dir_is_mounted - test if the tracing dir is already mounted
 * @mount: Mount it if it is not already mounted
 * @path: the path to the tracing directory if mounted or was mounted
 *
 * Returns 1 if the tracing directory is already mounted and 0 if it is not.
 * If @mount is set and it fails to mount, it returns -1.
 *
 * If path is not NULL, and the tracing directory is or was mounted, it holds
 * the path to the tracing directory. It must not be freed.
 */
int tracefs_tracing_dir_is_mounted(bool mount, const char **path)
{
	const char *dir;

	dir = find_tracing_dir(false, false);
	if (dir) {
		if (path)
			*path = dir;
		return 1;
	}
	if (!mount)
		return 0;

	dir = find_tracing_dir(false, mount);
	if (!dir)
		return -1;
	if (path)
		*path = dir;
	return 0;
}

/**
 * trace_find_tracing_dir - Find tracing directory
 * @debugfs: Boolean to just return the debugfs directory
 *
 * Returns string containing the full path to the system's tracing directory.
 * The string must be freed by free()
 */
__hidden char *trace_find_tracing_dir(bool debugfs)
{
	return find_tracing_dir(debugfs, false);
}

/**
 * tracefs_set_tracing_dir - Set location of the tracing directory
 * @tracing_dir: full path to the system's tracing directory mount point.
 *
 * Set the location to the system's tracing directory. This API should be used
 * to set a custom location of the tracing directory. There is no need to call
 * it if the location is standard, in that case the library will auto detect it.
 *
 * Returns 0 on success, -1 otherwise.
 */
int tracefs_set_tracing_dir(char *tracing_dir)
{
	if (custom_tracing_dir) {
		free(custom_tracing_dir);
		custom_tracing_dir = NULL;
	}

	if (tracing_dir) {
		custom_tracing_dir = strdup(tracing_dir);
		if (!custom_tracing_dir)
			return -1;
	}

	return 0;
}

/* Used to check if the directory is still mounted */
static int test_dir(const char *dir, const char *file)
{
	char path[strlen(dir) + strlen(file) + 2];
	struct stat st;

	sprintf(path, "%s/%s", dir, file);
	return stat(path, &st) < 0 ? 0 : 1;
}

/**
 * tracefs_tracing_dir - Get tracing directory
 *
 * Returns string containing the full path to the system's tracing directory.
 * The returned string must *not* be freed.
 */
const char *tracefs_tracing_dir(void)
{
	static const char *tracing_dir;

	/* Do not check custom_tracing_dir */
	if (custom_tracing_dir)
		return custom_tracing_dir;

	if (tracing_dir && test_dir(tracing_dir, "trace"))
		return tracing_dir;

	tracing_dir = find_tracing_dir(false, true);
	return tracing_dir;
}

/**
 * tracefs_debug_dir - Get debugfs directory path
 *
 * Returns string containing the full path to the system's debugfs directory.
 *
 * The returned string must *not* be freed.
 */
const char *tracefs_debug_dir(void)
{
	static const char *debug_dir;

	if (debug_dir && test_dir(debug_dir, "tracing"))
		return debug_dir;

	debug_dir = find_tracing_dir(true, true);
	return debug_dir;
}

/**
 * tracefs_get_tracing_file - Get tracing file
 * @name: tracing file name
 *
 * Returns string containing the full path to a tracing file in
 * the system's tracing directory.
 *
 * Must use tracefs_put_tracing_file() to free the returned string.
 */
char *tracefs_get_tracing_file(const char *name)
{
	const char *tracing;
	char *file;
	int ret;

	if (!name)
		return NULL;

	tracing = tracefs_tracing_dir();
	if (!tracing)
		return NULL;

	ret = asprintf(&file, "%s/%s", tracing, name);
	if (ret < 0)
		return NULL;

	return file;
}

/**
 * tracefs_put_tracing_file - Free tracing file or directory name
 *
 * Frees tracing file or directory, returned by
 * tracefs_get_tracing_file()API.
 */
void tracefs_put_tracing_file(char *name)
{
	free(name);
}

__hidden int str_read_file(const char *file, char **buffer, bool warn)
{
	char stbuf[BUFSIZ];
	char *buf = NULL;
	int size = 0;
	char *nbuf;
	int fd;
	int r;

	fd = open(file, O_RDONLY);
	if (fd < 0) {
		if (warn)
			tracefs_warning("File %s not found", file);
		return -1;
	}

	do {
		r = read(fd, stbuf, BUFSIZ);
		if (r <= 0)
			continue;
		nbuf = realloc(buf, size+r+1);
		if (!nbuf) {
			if (warn)
				tracefs_warning("Failed to allocate file buffer");
			size = -1;
			break;
		}
		buf = nbuf;
		memcpy(buf+size, stbuf, r);
		size += r;
	} while (r > 0);

	close(fd);
	if (r == 0 && size > 0) {
		buf[size] = '\0';
		*buffer = buf;
	} else
		free(buf);

	return size;
}

/**
 * tracefs_error_all - return the content of the error log
 * @instance: The instance to read the error log from (NULL for top level)
 *
 * Return NULL if the log is empty, or on error (where errno will be
 * set. Otherwise the content of the entire log is returned in a string
 * that must be freed with free().
 */
char *tracefs_error_all(struct tracefs_instance *instance)
{
	char *content;
	char *path;
	int size;

	errno = 0;

	path = tracefs_instance_get_file(instance, ERROR_LOG);
	if (!path)
		return NULL;
	size = str_read_file(path, &content, false);
	tracefs_put_tracing_file(path);

	if (size <= 0)
		return NULL;

	return content;
}

enum line_states {
	START,
	CARROT,
};

/**
 * tracefs_error_last - return the last error logged
 * @instance: The instance to read the error log from (NULL for top level)
 *
 * Return NULL if the log is empty, or on error (where errno will be
 * set. Otherwise a string containing the content of the last error shown
* in the log that must be freed with free().
 */
char *tracefs_error_last(struct tracefs_instance *instance)
{
	enum line_states state = START;
	char *content;
	char *ret;
	bool done = false;
	int size;
	int i;

	content = tracefs_error_all(instance);
	if (!content)
		return NULL;

	size = strlen(content);
	if (!size) /* Should never happen */
		return content;

	for (i = size - 1; i > 0; i--) {
		switch (state) {
		case START:
			if (content[i] == '\n') {
				/* Remove extra new lines */
				content[i] = '\0';
				break;
			}
			if (content[i] == '^')
				state = CARROT;
			break;
		case CARROT:
			if (content[i] == '\n') {
				/* Remember last new line */
				size = i;
				break;
			}
			if (content[i] == '^') {
				/* Go just passed the last newline */
				i = size + 1;
				done = true;
			}
			break;
		}
		if (done)
			break;
	}

	if (i) {
		ret = strdup(content + i);
		free(content);
	} else {
		ret = content;
	}

	return ret;
}

/**
 * tracefs_error_clear - clear the error log of an instance
 * @instance: The instance to clear (NULL for top level)
 *
 * Clear the content of the error log.
 *
 * Returns 0 on success, -1 otherwise.
 */
int tracefs_error_clear(struct tracefs_instance *instance)
{
	return tracefs_instance_file_clear(instance, ERROR_LOG);
}

/**
 * tracefs_list_free - free list if strings, returned by APIs
 *			tracefs_event_systems()
 *			tracefs_system_events()
 *
 *@list pointer to a list of strings, the last one must be NULL
 */
void tracefs_list_free(char **list)
{
	int i;

	if (!list)
		return;

	for (i = 0; list[i]; i++)
		free(list[i]);

	/* The allocated list is before the user visible portion */
	list--;
	free(list);
}


__hidden char ** trace_list_create_empty(void)
{
	char **list;

	list = calloc(2, sizeof(*list));

	return list ? &list[1] : NULL;
}

/**
 * tracefs_list_add - create or extend a string list
 * @list: The list to add to (NULL to create a new one)
 * @string: The string to append to @list.
 *
 * If @list is NULL, a new list is created with the first element
 * a copy of @string, and the second element is NULL.
 *
 * If @list is not NULL, it is then reallocated to include
 * a new element and a NULL terminator, and will return the new
 * allocated array on success, and the one passed in should be
 * ignored.
 *
 * Returns an allocated string array that must be freed with
 * tracefs_list_free() on success. On failure, NULL is returned
 * and the @list is untouched.
 */
char **tracefs_list_add(char **list, const char *string)
{
	unsigned long size = 0;
	char *str = strdup(string);
	char **new_list;

	if (!str)
		return NULL;

	/*
	 * The returned list is really the address of the
	 * second entry of the list (&list[1]), the first
	 * entry contains the number of strings in the list.
	 */
	if (list) {
		list--;
		size = *(unsigned long *)list;
	}

	new_list = realloc(list, sizeof(*list) * (size + 3));
	if (!new_list) {
		free(str);
		return NULL;
	}

	list = new_list;
	list[0] = (char *)(size + 1);
	list++;
	list[size++] = str;
	list[size] = NULL;

	return list;
}

/*
 * trace_list_pop - Removes the last string added
 * @list: The list to remove the last event from
 *
 * Returns 0 on success, -1 on error.
 * Returns 1 if the list is empty or NULL.
 */
__hidden int trace_list_pop(char **list)
{
	unsigned long size;

	if (!list || list[0])
		return 1;

	list--;
	size = *(unsigned long *)list;
	/* size must be greater than zero */
	if (!size)
		return -1;
	size--;
	*list = (char *)size;
	list++;
	list[size] = NULL;
	return 0;
}

/**
 * tracefs_list_size - Return the number of strings in the list
 * @list: The list to determine the size.
 *
 * Returns the number of elements in the list.
 * If @list is NULL, then zero is returned.
 */
int tracefs_list_size(char **list)
{
	if (!list)
		return 0;

	list--;
	return (int)*(unsigned long *)list;
}

/**
 * tracefs_tracer_available - test if a tracer is available
 * @tracing_dir: The directory that contains the tracing directory
 * @tracer: The name of the tracer
 *
 * Return true if the tracer is available
 */
bool tracefs_tracer_available(const char *tracing_dir, const char *tracer)
{
	bool ret = false;
	char **tracers = NULL;
	int i;

	tracers = tracefs_tracers(tracing_dir);
	if (!tracers)
		return false;

	for (i = 0; tracers[i]; i++) {
		if (strcmp(tracer, tracers[i]) == 0) {
			ret = true;
			break;
		}
	}

	tracefs_list_free(tracers);
	return ret;
}
