/*
 * Copyright (C) 2017-2025 Intel Corporation
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  * Neither the name of Intel Corporation nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "pt_sb_session.h"
#include "pt_sb_context.h"
#include "pt_sb_decoder.h"

#include "libipt-sb.h"

#include <string.h>
#include <stdlib.h>

#if defined(_MSC_VER) && (_MSC_VER < 1900)
#  define snprintf _snprintf_c
#endif


struct pt_sb_session *pt_sb_alloc(struct pt_image_section_cache *iscache)
{
	struct pt_sb_session *session;
	struct pt_image *kernel;

	kernel = pt_image_alloc("kernel");
	if (!kernel)
		return NULL;

	session = malloc(sizeof(*session));
	if (!session) {
		pt_image_free(kernel);
		return NULL;
	}

	memset(session, 0, sizeof(*session));
	session->iscache = iscache;
	session->kernel = kernel;

	return session;
}

static void pt_sb_free_decoder(struct pt_sb_decoder *decoder)
{
	void (*dtor)(void *);

	if (!decoder)
		return;

	dtor = decoder->dtor;
	if (dtor)
		dtor(decoder->priv);

	free(decoder);
}

static void pt_sb_free_decoder_list(struct pt_sb_decoder *list)
{
	while (list) {
		struct pt_sb_decoder *trash;

		trash = list;
		list = trash->next;

		pt_sb_free_decoder(trash);
	}
}

void pt_sb_free(struct pt_sb_session *session)
{
	struct pt_sb_context *context;

	if (!session)
		return;

	pt_sb_free_decoder_list(session->decoders);
	pt_sb_free_decoder_list(session->waiting);
	pt_sb_free_decoder_list(session->retired);
	pt_sb_free_decoder_list(session->removed);

	context = session->contexts;
	while (context) {
		struct pt_sb_context *trash;

		trash = context;
		context = trash->next;

		(void) pt_sb_ctx_put(trash);
	}

	pt_image_free(session->kernel);

	free(session);
}

struct pt_image_section_cache *pt_sb_iscache(struct pt_sb_session *session)
{
	if (!session)
		return NULL;

	return session->iscache;
}

struct pt_image *pt_sb_kernel_image(struct pt_sb_session *session)
{
	if (!session)
		return NULL;

	return session->kernel;
}

static int pt_sb_add_context_by_pid(struct pt_sb_context **pcontext,
				    struct pt_sb_session *session, uint32_t pid)
{
	struct pt_sb_context *context;
	struct pt_image *kernel;
	char iname[16];
	int errcode;

	if (!pcontext || !session)
		return -pte_invalid;

	kernel = pt_sb_kernel_image(session);
	if (!kernel)
		return -pte_internal;

	memset(iname, 0, sizeof(iname));
	(void) snprintf(iname, sizeof(iname), "pid-%x", pid);

	context = pt_sb_ctx_alloc(iname);
	if (!context)
		return -pte_nomem;

	errcode = pt_image_copy(context->image, kernel);
	if (errcode < 0) {
		(void) pt_sb_ctx_put(context);
		return errcode;
	}

	context->next = session->contexts;
	context->pid = pid;

	session->contexts = context;
	*pcontext = context;

	return 0;
}

int pt_sb_get_context_by_pid(struct pt_sb_context **context,
			     struct pt_sb_session *session, uint32_t pid)
{
	int status;

	if (!context || !session)
		return -pte_invalid;

	status = pt_sb_find_context_by_pid(context, session, pid);
	if (status < 0)
		return status;

	if (*context)
		return 0;

	return pt_sb_add_context_by_pid(context, session, pid);
}

int pt_sb_find_context_by_pid(struct pt_sb_context **pcontext,
			      struct pt_sb_session *session, uint32_t pid)
{
	struct pt_sb_context *ctx;

	if (!pcontext || !session)
		return -pte_invalid;

	for (ctx = session->contexts; ctx; ctx = ctx->next) {
		if (ctx->pid == pid)
			break;
	}

	*pcontext = ctx;

	return 0;
}

int pt_sb_remove_context(struct pt_sb_session *session,
			 struct pt_sb_context *context)
{
	struct pt_sb_context **pnext, *ctx;

	if (!session || !context)
		return -pte_invalid;

	pnext = &session->contexts;
	for (ctx = *pnext; ctx; pnext = &ctx->next, ctx = *pnext) {
		if (ctx == context)
			break;
	}

	if (!ctx)
		return -pte_nosync;

	*pnext = ctx->next;

	return pt_sb_ctx_put(ctx);
}

int pt_sb_alloc_decoder(struct pt_sb_session *session,
			const struct pt_sb_decoder_config *config)
{
	struct pt_sb_decoder *decoder;

	if (!session || !config)
		return -pte_invalid;

	decoder = malloc(sizeof(*decoder));
	if (!decoder)
		return -pte_nomem;

	memset(decoder, 0, sizeof(*decoder));
	decoder->next = session->waiting;
	decoder->fetch = config->fetch;
	decoder->apply = config->apply;
	decoder->print = config->print;
	decoder->dtor = config->dtor;
	decoder->priv = config->priv;
	decoder->primary = config->primary;

	session->waiting = decoder;

	return 0;
}

/* Add a new decoder to a list of decoders.
 *
 * Decoders in @list are ordered by their @tsc (ascending) and @list does not
 * contain @decoder.  Find the right place for @decoder at add it to @list.
 *
 * Returns zero on success, a negative error code otherwise.
 */
static int pt_sb_add_decoder(struct pt_sb_decoder **list,
			     struct pt_sb_decoder *decoder)
{
	struct pt_sb_decoder *cand;
	uint64_t tsc;

	if (!list || !decoder || decoder->next)
		return -pte_internal;

	tsc = decoder->tsc;
	for (cand = *list; cand; list = &cand->next, cand = *list) {
		if (tsc <= cand->tsc)
			break;
	}

	decoder->next = cand;
	*list = decoder;

	return 0;
}

static int pt_sb_fetch(struct pt_sb_session *session,
		       struct pt_sb_decoder *decoder)
{
	int (*fetch)(struct pt_sb_session *, uint64_t *, void *);

	if (!decoder)
		return -pte_internal;

	fetch = decoder->fetch;
	if (!fetch)
		return -pte_bad_config;

	return fetch(session, &decoder->tsc, decoder->priv);
}

static int pt_sb_print(struct pt_sb_session *session,
		       struct pt_sb_decoder *decoder, FILE *stream,
		       uint32_t flags)
{
	int (*print)(struct pt_sb_session *, FILE *, uint32_t, void *);

	if (!decoder)
		return -pte_internal;

	print = decoder->print;
	if (!print)
		return -pte_bad_config;

	return print(session, stream, flags, decoder->priv);
}

static int pt_sb_apply(struct pt_sb_session *session, struct pt_image **image,
		       struct pt_sb_decoder *decoder,
		       const struct pt_event *event)
{
	int (*apply)(struct pt_sb_session *, struct pt_image **,
		     const struct pt_event *, void *);

	if (!decoder || !event)
		return -pte_internal;

	apply = decoder->apply;
	if (!apply)
		return -pte_bad_config;

	if (!decoder->primary)
		image = NULL;

	return apply(session, image, event, decoder->priv);
}

int pt_sb_init_decoders(struct pt_sb_session *session)
{
	struct pt_sb_decoder *decoder;

	if (!session)
		return -pte_invalid;

	decoder = session->waiting;
	while (decoder) {
		int errcode;

		session->waiting = decoder->next;
		decoder->next = NULL;

		errcode = pt_sb_fetch(session, decoder);
		if (errcode < 0) {
			/* Fetch errors remove @decoder.  In this case, they
			 * prevent it from being added in the first place.
			 */
			pt_sb_free_decoder(decoder);
		} else {
			errcode = pt_sb_add_decoder(&session->decoders,
						    decoder);
			if (errcode < 0)
				return errcode;
		}

		decoder = session->waiting;
	}

	return 0;
}

/* Copy an event provided by an unknown version of libipt.
 *
 * Copy at most @size bytes of @uevent into @event and zero-initialize any
 * additional bytes in @event not covered by @uevent.
 *
 * Returns zero on success, a negative error code otherwise.
 */
static int pt_sb_event_from_user(struct pt_event *event,
				 const struct pt_event *uevent, size_t size)
{
	if (!event || !uevent)
		return -pte_internal;

	if (size < offsetof(struct pt_event, reserved))
		return -pte_invalid;

	/* Ignore fields in the user's event we don't know; zero out fields the
	 * user didn't know about.
	 */
	if (sizeof(*event) < size)
		size = sizeof(*event);
	else
		memset(((uint8_t *) event) + size, 0, sizeof(*event) - size);

	/* Copy (portions of) the user's event. */
	memcpy(event, uevent, size);

	return 0;
}

static int pt_sb_event_present(struct pt_sb_session *session,
			       struct pt_image **image,
			       struct pt_sb_decoder **pnext,
			       const struct pt_event *event)
{
	struct pt_sb_decoder *decoder;
	int errcode;

	if (!session || !pnext)
		return -pte_internal;

	decoder = *pnext;
	while (decoder) {
		errcode = pt_sb_apply(session, image, decoder, event);
		if (errcode < 0) {
			struct pt_sb_decoder *trash;

			trash = decoder;
			decoder = trash->next;
			*pnext = decoder;

			trash->next = session->removed;
			session->removed = trash;
			continue;
		}

		pnext = &decoder->next;
		decoder = *pnext;
	}

	return 0;
}

int pt_sb_event(struct pt_sb_session *session, struct pt_image **image,
		const struct pt_event *uevent, size_t size, FILE *stream,
		uint32_t flags)
{
	struct pt_sb_decoder *decoder;
	struct pt_event event;
	int errcode;

	if (!session || !uevent)
		return -pte_invalid;

	errcode = pt_sb_event_from_user(&event, uevent, size);
	if (errcode < 0)
		return errcode;

	/* In the initial round, we present the event to all decoders with
	 * records for a smaller or equal timestamp.
	 *
	 * We only need to look at the first decoder.  We remove it from the
	 * list and ask it to apply the event.  Then, we ask it to fetch the
	 * next record and re-add it to the list according to that next record's
	 * timestamp.
	 */
	for (;;) {
		decoder = session->decoders;
		if (!decoder)
			break;

		/* We don't check @event.has_tsc to support sideband
		 * correlation based on relative (non-wall clock) time.
		 */
		if (event.tsc < decoder->tsc)
			break;

		session->decoders = decoder->next;
		decoder->next = NULL;

		if (stream) {
			errcode = pt_sb_print(session, decoder, stream, flags);
			if (errcode < 0) {
				decoder->next = session->removed;
				session->removed = decoder;
				continue;
			}
		}

		errcode = pt_sb_apply(session, image, decoder, &event);
		if (errcode < 0) {
			decoder->next = session->removed;
			session->removed = decoder;
			continue;
		}

		errcode = pt_sb_fetch(session, decoder);
		if (errcode < 0) {
			if (errcode == -pte_eos) {
				decoder->next = session->retired;
				session->retired = decoder;
			} else {
				decoder->next = session->removed;
				session->removed = decoder;
			}

			continue;
		}

		errcode = pt_sb_add_decoder(&session->decoders, decoder);
		if (errcode < 0)
			return errcode;
	}

	/* In the second round, we present the event to all decoders.
	 *
	 * This allows decoders to postpone actions until an appropriate event,
	 * e.g entry into or exit from the kernel.
	 */
	errcode = pt_sb_event_present(session, image, &session->decoders,
				      &event);
	if (errcode < 0)
		return errcode;

	return pt_sb_event_present(session, image, &session->retired, &event);
}

int pt_sb_dump(struct pt_sb_session *session, FILE *stream, uint32_t flags,
	       uint64_t tsc)
{
	struct pt_sb_decoder *decoder;
	int errcode;

	if (!session || !stream)
		return -pte_invalid;

	for (;;) {
		decoder = session->decoders;
		if (!decoder)
			break;

		if (tsc < decoder->tsc)
			break;

		session->decoders = decoder->next;
		decoder->next = NULL;

		errcode = pt_sb_print(session, decoder, stream, flags);
		if (errcode < 0) {
			decoder->next = session->removed;
			session->removed = decoder;
			continue;
		}

		errcode = pt_sb_fetch(session, decoder);
		if (errcode < 0) {
			decoder->next = session->removed;
			session->removed = decoder;
			continue;
		}

		errcode = pt_sb_add_decoder(&session->decoders, decoder);
		if (errcode < 0)
			return errcode;
	}

	return 0;
}

pt_sb_ctx_switch_notifier_t *
pt_sb_notify_switch(struct pt_sb_session *session,
		    pt_sb_ctx_switch_notifier_t *notifier, void *priv)
{
	pt_sb_ctx_switch_notifier_t *old;

	if (!session)
		return NULL;

	old = session->notify_switch_to;

	session->notify_switch_to = notifier;
	session->priv_switch_to = priv;

	return old;
}

pt_sb_error_notifier_t *
pt_sb_notify_error(struct pt_sb_session *session,
		   pt_sb_error_notifier_t *notifier, void *priv)
{
	pt_sb_error_notifier_t *old;

	if (!session)
		return NULL;

	old = session->notify_error;

	session->notify_error = notifier;
	session->priv_error = priv;

	return old;
}

int pt_sb_error(const struct pt_sb_session *session, int errcode,
		const char *filename, uint64_t offset)
{
	pt_sb_error_notifier_t *notifier;

	if (!session)
		return -pte_internal;

	notifier = session->notify_error;
	if (!notifier)
		return 0;

	return notifier(errcode, filename, offset, session->priv_error);
}

const char *pt_sb_errstr(enum pt_sb_error_code errcode)
{
	switch (errcode) {
	case ptse_ok:
		return "OK";

	case ptse_lost:
		return "sideband lost";

	case ptse_trace_lost:
		return "trace lost";

	case ptse_section_lost:
		return "image section lost";
	}

	return "bad errcode";
}
