/*
 * 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 "libipt-sb.h"


#ifndef FEATURE_PEVENT

int pt_sb_alloc_pevent_decoder(struct pt_sb_session *session,
			       const struct pt_sb_pevent_config *config)
{
	(void) session;
	(void) config;

	return -pte_not_supported;
}

#else /* FEATURE_PEVENT */

#include "pt_sb_pevent.h"
#include "pt_sb_session.h"
#include "pt_sb_context.h"
#include "pt_sb_file.h"
#include "pt_compiler.h"

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

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


#ifndef FEATURE_ELF

static int elf_get_abi(FILE *file)
{
	if (!file)
		return -pte_internal;

	return pt_sb_abi_unknown;
}

#else /* FEATURE_ELF */

#include <elf.h>


static int elf_get_abi(FILE *file)
{
	uint8_t e_ident[EI_NIDENT];
	size_t count;
	int status;

	if (!file)
		return -pte_internal;

	status = fseek(file, 0, SEEK_SET);
	if (status < 0)
		return pt_sb_abi_unknown;

	count = fread(e_ident, sizeof(e_ident), 1, file);
	if (count != 1)
		return pt_sb_abi_unknown;

	status = memcmp(e_ident, ELFMAG, SELFMAG);
	if (status != 0)
		return pt_sb_abi_unknown;

	if (e_ident[EI_VERSION] != EV_CURRENT)
		return pt_sb_abi_unknown;

	switch (e_ident[EI_CLASS]) {
	default:
		break;

	case ELFCLASS64:
		return pt_sb_abi_x64;

	case ELFCLASS32: {
		Elf32_Ehdr ehdr;

		status = fseek(file, 0, SEEK_SET);
		if (status < 0)
			break;

		count = fread(&ehdr, sizeof(ehdr), 1, file);
		if (count != 1)
			break;

		switch (ehdr.e_machine) {
		default:
			break;

		case EM_386:
			return pt_sb_abi_ia32;

		case EM_X86_64:
			return pt_sb_abi_x32;
		}
	}
		break;
	}

	return pt_sb_abi_unknown;
}

#endif /* FEATURE_ELF */

static int pt_sb_pevent_error(const struct pt_sb_session *session, int errcode,
			      const struct pt_sb_pevent_priv *priv)
{
	const char *filename;
	uint64_t offset;

	filename = NULL;
	offset = 0ull;

	if (priv) {
		const uint8_t *pos, *begin;

		pos = priv->current;
		if (!pos)
			pos = priv->next;

		begin = priv->begin;
		if (pos < begin)
			return -pte_internal;

		filename = priv->filename;
		offset = (uint64_t) (int64_t) (pos - begin);
	}

	return pt_sb_error(session, errcode, filename, offset);
}

static int pt_sb_pevent_track_abi(struct pt_sb_context *context,
				  const char *filename)
{
	FILE *file;
	int abi;

	if (!context || !filename)
		return -pte_internal;

	if (context->abi)
		return 0;

	file = fopen(filename, "rb");
	if (!file)
		return 0;

	abi = elf_get_abi(file);

	fclose(file);

	if (abi < 0)
		return abi;

	context->abi = (enum pt_sb_abi) abi;

	return 0;
}

static int pt_sb_pevent_find_vdso(const char **pvdso,
				  const struct pt_sb_pevent_priv *priv,
				  const struct pt_sb_context *context)
{
	const char *vdso;

	if (!pvdso || !priv || !context)
		return -pte_internal;

	vdso = NULL;

	switch (context->abi) {
	case pt_sb_abi_unknown:
		break;

	case pt_sb_abi_x64:
		vdso = priv->vdso_x64;
		break;

	case pt_sb_abi_x32:
		vdso = priv->vdso_x32;
		break;

	case pt_sb_abi_ia32:
		vdso = priv->vdso_ia32;
		break;
	}

	if (!vdso)
		return -pte_bad_config;

	*pvdso = vdso;

	return 0;
}

static void pt_sb_pevent_dtor(void *priv_arg)
{
	struct pt_sb_pevent_priv *priv;
	struct pt_sb_context *context;

	priv = (struct pt_sb_pevent_priv *) priv_arg;
	if (!priv)
		return;

	context = priv->next_context;
	if (context)
		pt_sb_ctx_put(context);

	context = priv->context;
	if (context)
		pt_sb_ctx_put(context);

	free(priv->filename);
	free(priv->sysroot);
	free(priv->vdso_x64);
	free(priv->vdso_x32);
	free(priv->vdso_ia32);
	free(priv->begin);
	free(priv);
}

static int pt_sb_pevent_init_path(char **dst, const char *src)
{
	size_t len;
	char *copy;

	if (!dst)
		return -pte_internal;

	if (!src) {
		*dst = NULL;
		return 0;
	}

	len = strnlen(src, FILENAME_MAX);
	if (len == FILENAME_MAX)
		return -pte_invalid;

	len += 1;
	copy = malloc(len);
	if (!copy)
		return -pte_nomem;

	memcpy(copy, src, len);

	*dst = copy;

	return 0;
}

int pt_sb_pevent_init(struct pt_sb_pevent_priv *priv,
		      const struct pt_sb_pevent_config *config)
{
	const char *filename;
	size_t size;
	void *buffer;
	int errcode;

	if (!priv || !config)
		return -pte_internal;

	/* This is the first version - we need all the fields. */
	if (config->size < sizeof(*config))
		return -pte_invalid;

	filename = config->filename;
	if (!filename)
		return -pte_invalid;

	buffer = NULL;
	size = 0;
	errcode = pt_sb_file_load(&buffer, &size, filename,
				  config->begin, config->end);
	if (errcode < 0)
		return errcode;

	memset(priv, 0, sizeof(*priv));
	priv->begin = (uint8_t *) buffer;
	priv->end = (uint8_t *) buffer + size;
	priv->next = (uint8_t *) buffer;

	errcode = pt_sb_pevent_init_path(&priv->filename, filename);
	if (errcode < 0) {
		pt_sb_pevent_dtor(priv);
		return errcode;
	}

	errcode = pt_sb_pevent_init_path(&priv->sysroot, config->sysroot);
	if (errcode < 0) {
		pt_sb_pevent_dtor(priv);
		return errcode;
	}

	errcode = pt_sb_pevent_init_path(&priv->vdso_x64, config->vdso_x64);
	if (errcode < 0) {
		pt_sb_pevent_dtor(priv);
		return errcode;
	}

	errcode = pt_sb_pevent_init_path(&priv->vdso_x32, config->vdso_x32);
	if (errcode < 0) {
		pt_sb_pevent_dtor(priv);
		return errcode;
	}

	errcode = pt_sb_pevent_init_path(&priv->vdso_ia32, config->vdso_ia32);
	if (errcode < 0) {
		pt_sb_pevent_dtor(priv);
		return errcode;
	}

	pev_config_init(&priv->pev);
	priv->pev.sample_type = config->sample_type;
	priv->pev.time_shift = config->time_shift;
	priv->pev.time_mult = config->time_mult;
	priv->pev.time_zero = config->time_zero;

	priv->kernel_start = config->kernel_start;
	priv->tsc_offset = config->tsc_offset;
	priv->location = ploc_unknown;

#if (LIBIPT_SB_VERSION >= 0x201)
	if (pev_config_has(config, sample_config))
		priv->pev.sample_config = config->sample_config;
#endif

	return 0;
}

static int pt_sb_pevent_fetch(uint64_t *ptsc, struct pt_sb_pevent_priv *priv)
{
	struct pev_event *event;
	const uint8_t *pos;
	uint64_t tsc, offset;
	int size;

	if (!ptsc || !priv)
		return -pte_internal;

	pos = priv->next;
	event = &priv->event;

	/* Consume the current record early so we get the offset right when
	 * diagnosing fetch errors.
	 */
	priv->current = pos;

	size = pev_read(event, pos, priv->end, &priv->pev);
	if (size < 0)
		return size;

	priv->next = pos + size;

	/* If we don't have a time sample, set @ptsc to zero to process the
	 * record immediately.
	 */
	if (!event->sample.time) {
		*ptsc = 0ull;
		return 0;
	}

	/* Subtract a pre-defined offset to cause sideband events from this
	 * channel to be applied a little earlier.
	 *
	 * We don't want @tsc to wrap around when subtracting @offset, though.
	 * This would suddenly push the event very far out and essentially block
	 * this sideband channel.
	 *
	 * On the other hand, we want to allow 'negative' offsets.  And for
	 * those, we want to avoid wrapping around in the other direction.
	 */
	offset = priv->tsc_offset;
	tsc = event->sample.tsc;
	if (offset <= tsc)
		tsc -= offset;
	else {
		if (0ll <= (int64_t) offset)
			tsc = 0ull;
		else {
			if (tsc <= offset)
				tsc -= offset;
			else
				tsc = UINT64_MAX;
		}
	}

	/* We update the event record's timestamp, as well, so we will print the
	 * updated tsc and apply the event at the right time.
	 *
	 * Note that we only update our copy in @priv, not the sideband stream.
	 */
	event->sample.tsc = tsc;
	*ptsc = tsc;

	return 0;
}

static int pt_sb_pevent_print_event(const struct pev_event *event,
				    FILE *stream, uint32_t flags)
{
	if (!event)
		return -pte_internal;

	switch (event->type) {
	default:
		if (flags & ptsbp_compact)
			fprintf(stream, "UNKNOWN (%x, %x)", event->type,
				event->misc);

		if (flags & ptsbp_verbose) {
			fprintf(stream, "UNKNOWN");
			fprintf(stream, "\n  type: %x", event->type);
			fprintf(stream, "\n  misc: %x", event->misc);
		}

		break;

	case PERF_RECORD_MMAP: {
		const struct pev_record_mmap *mmap;

		mmap = event->record.mmap;
		if (!mmap)
			return -pte_bad_packet;

		if (flags & ptsbp_compact)
			fprintf(stream, "PERF_RECORD_MMAP  %x/%x, %" PRIx64
				", %" PRIx64 ", %" PRIx64 ", %s",
				mmap->pid, mmap->tid, mmap->addr, mmap->len,
				mmap->pgoff, mmap->filename);

		if (flags & ptsbp_verbose) {
			fprintf(stream, "PERF_RECORD_MMAP");
			fprintf(stream, "\n  pid: %x", mmap->pid);
			fprintf(stream, "\n  tid: %x", mmap->tid);
			fprintf(stream, "\n  addr: %" PRIx64, mmap->addr);
			fprintf(stream, "\n  len: %" PRIx64, mmap->len);
			fprintf(stream, "\n  pgoff: %" PRIx64, mmap->pgoff);
			fprintf(stream, "\n  filename: %s", mmap->filename);
		}
	}
		break;

	case PERF_RECORD_LOST: {
		const struct pev_record_lost *lost;

		lost = event->record.lost;
		if (!lost)
			return -pte_bad_packet;

		if (flags & ptsbp_compact)
			fprintf(stream, "PERF_RECORD_LOST  %" PRIx64 ", %"
				PRIx64, lost->id, lost->lost);

		if (flags & ptsbp_verbose) {
			fprintf(stream, "PERF_RECORD_LOST");
			fprintf(stream, "\n  id: %" PRIx64, lost->id);
			fprintf(stream, "\n  lost: %" PRIx64, lost->lost);
		}
	}
		break;

	case PERF_RECORD_COMM: {
		const struct pev_record_comm *comm;
		const char *sfx;

		comm = event->record.comm;
		if (!comm)
			return -pte_bad_packet;

		sfx = event->misc & PERF_RECORD_MISC_COMM_EXEC ? ".EXEC" : "";

		if (flags & ptsbp_compact)
			fprintf(stream, "PERF_RECORD_COMM%s  %x/%x, %s", sfx,
				comm->pid, comm->tid, comm->comm);

		if (flags & ptsbp_verbose) {
			fprintf(stream, "PERF_RECORD_COMM%s", sfx);
			fprintf(stream, "\n  pid: %x", comm->pid);
			fprintf(stream, "\n  tid: %x", comm->tid);
			fprintf(stream, "\n  comm: %s", comm->comm);
		}
	}
		break;

	case PERF_RECORD_EXIT: {
		const struct pev_record_exit *exit;

		exit = event->record.exit;
		if (!exit)
			return -pte_bad_packet;

		if (flags & ptsbp_compact)
			fprintf(stream, "PERF_RECORD_EXIT  %x/%x, %x/%x, %"
				PRIx64, exit->pid, exit->tid, exit->ppid,
				exit->ptid, exit->time);

		if (flags & ptsbp_verbose) {
			fprintf(stream, "PERF_RECORD_EXIT");
			fprintf(stream, "\n  pid: %x", exit->pid);
			fprintf(stream, "\n  ppid: %x", exit->ppid);
			fprintf(stream, "\n  tid: %x", exit->tid);
			fprintf(stream, "\n  ptid: %x", exit->ptid);
			fprintf(stream, "\n  time: %" PRIx64, exit->time);
		}
	}
		break;

	case PERF_RECORD_THROTTLE: {
		const struct pev_record_throttle *throttle;

		throttle = event->record.throttle;
		if (!throttle)
			return -pte_bad_packet;

		if (flags & ptsbp_compact)
			fprintf(stream, "PERF_RECORD_THROTTLE  %" PRIx64 ", %"
				PRIx64 ", %" PRIx64, throttle->time,
				throttle->id, throttle->stream_id);

		if (flags & ptsbp_verbose) {
			fprintf(stream, "PERF_RECORD_THROTTLE");
			fprintf(stream, "\n  time: %" PRIx64, throttle->time);
			fprintf(stream, "\n  id: %" PRIx64, throttle->id);
			fprintf(stream, "\n  stream_id: %" PRIx64,
				throttle->stream_id);
		}
	}
		break;

	case PERF_RECORD_UNTHROTTLE: {
		const struct pev_record_throttle *throttle;

		throttle = event->record.throttle;
		if (!throttle)
			return -pte_bad_packet;

		if (flags & ptsbp_compact)
			fprintf(stream, "PERF_RECORD_UNTHROTTLE  %" PRIx64
				", %" PRIx64 ", %" PRIx64, throttle->time,
				throttle->id, throttle->stream_id);

		if (flags & ptsbp_verbose) {
			fprintf(stream, "PERF_RECORD_UNTHROTTLE");
			fprintf(stream, "\n  time: %" PRIx64, throttle->time);
			fprintf(stream, "\n  id: %" PRIx64, throttle->id);
			fprintf(stream, "\n  stream_id: %" PRIx64,
				throttle->stream_id);
		}
	}
		break;

	case PERF_RECORD_FORK: {
		const struct pev_record_fork *fork;

		fork = event->record.fork;
		if (!fork)
			return -pte_bad_packet;

		if (flags & ptsbp_compact)
			fprintf(stream, "PERF_RECORD_FORK  %x/%x, %x/%x, %"
				PRIx64, fork->pid, fork->tid, fork->ppid,
				fork->ptid, fork->time);

		if (flags & ptsbp_verbose) {
			fprintf(stream, "PERF_RECORD_FORK");
			fprintf(stream, "\n  pid: %x", fork->pid);
			fprintf(stream, "\n  ppid: %x", fork->ppid);
			fprintf(stream, "\n  tid: %x", fork->tid);
			fprintf(stream, "\n  ptid: %x", fork->ptid);
			fprintf(stream, "\n  time: %" PRIx64, fork->time);
		}
	}
		break;

	case PERF_RECORD_MMAP2: {
		const struct pev_record_mmap2 *mmap2;

		mmap2 = event->record.mmap2;
		if (!mmap2)
			return -pte_bad_packet;

		if (flags & ptsbp_compact)
			fprintf(stream, "PERF_RECORD_MMAP2  %x/%x, %" PRIx64
				", %" PRIx64 ", %" PRIx64 ", %x, %x, %" PRIx64
				", %" PRIx64 ", %x, %x, %s", mmap2->pid,
				mmap2->tid, mmap2->addr, mmap2->len,
				mmap2->pgoff, mmap2->maj, mmap2->min,
				mmap2->ino, mmap2->ino_generation, mmap2->prot,
				mmap2->flags, mmap2->filename);

		if (flags & ptsbp_verbose) {
			fprintf(stream, "PERF_RECORD_MMAP2");
			fprintf(stream, "\n  pid: %x", mmap2->pid);
			fprintf(stream, "\n  tid: %x", mmap2->tid);
			fprintf(stream, "\n  addr: %" PRIx64, mmap2->addr);
			fprintf(stream, "\n  len: %" PRIx64, mmap2->len);
			fprintf(stream, "\n  pgoff: %" PRIx64, mmap2->pgoff);
			fprintf(stream, "\n  maj: %x", mmap2->maj);
			fprintf(stream, "\n  min: %x", mmap2->min);
			fprintf(stream, "\n  ino: %" PRIx64, mmap2->ino);
			fprintf(stream, "\n  ino_generation: %" PRIx64,
				mmap2->ino_generation);
			fprintf(stream, "\n  prot: %x", mmap2->prot);
			fprintf(stream, "\n  flags: %x", mmap2->flags);
			fprintf(stream, "\n  filename: %s", mmap2->filename);
		}
	}
		break;

	case PERF_RECORD_AUX: {
		const struct pev_record_aux *aux;
		const char *sfx;

		aux = event->record.aux;
		if (!aux)
			return -pte_bad_packet;

		sfx = aux->flags & PERF_AUX_FLAG_TRUNCATED ? ".TRUNCATED" : "";

		if (flags & ptsbp_compact)
			fprintf(stream, "PERF_RECORD_AUX%s  %" PRIx64 ", %"
				PRIx64 ", %" PRIx64, sfx, aux->aux_offset,
				aux->aux_size, aux->flags);

		if (flags & ptsbp_verbose) {
			fprintf(stream, "PERF_RECORD_AUX%s", sfx);
			fprintf(stream, "\n  aux offset: %" PRIx64,
				aux->aux_offset);
			fprintf(stream, "\n  aux size: %" PRIx64,
				aux->aux_size);
			fprintf(stream, "\n  flags: %" PRIx64, aux->flags);
		}
	}
		break;

	case PERF_RECORD_ITRACE_START: {
		const struct pev_record_itrace_start *itrace_start;

		itrace_start = event->record.itrace_start;
		if (!itrace_start)
			return -pte_bad_packet;

		if (flags & ptsbp_compact)
			fprintf(stream, "PERF_RECORD_ITRACE_START  %x/%x",
				itrace_start->pid, itrace_start->tid);

		if (flags & ptsbp_verbose) {
			fprintf(stream, "PERF_RECORD_ITRACE_START");
			fprintf(stream, "\n  pid: %x", itrace_start->pid);
			fprintf(stream, "\n  tid: %x", itrace_start->tid);
		}
	}
		break;

	case PERF_RECORD_LOST_SAMPLES: {
		const struct pev_record_lost_samples *lost_samples;

		lost_samples = event->record.lost_samples;
		if (!lost_samples)
			return -pte_bad_packet;

		if (flags & ptsbp_compact)
			fprintf(stream, "PERF_RECORD_LOST_SAMPLES  %" PRIx64,
				lost_samples->lost);

		if (flags & ptsbp_verbose) {
			fprintf(stream, "PERF_RECORD_LOST_SAMPLES");
			fprintf(stream, "\n  lost: %" PRIx64,
				lost_samples->lost);
		}

	}
		break;

	case PERF_RECORD_SWITCH: {
		const char *sfx;

		sfx = event->misc & PERF_RECORD_MISC_SWITCH_OUT ? "OUT" : "IN";

		if (flags & (ptsbp_compact | ptsbp_verbose))
			fprintf(stream, "PERF_RECORD_SWITCH.%s", sfx);
	}
		break;

	case PERF_RECORD_SWITCH_CPU_WIDE: {
		const struct pev_record_switch_cpu_wide *switch_cpu_wide;
		const char *sfx, *pfx;

		if (event->misc & PERF_RECORD_MISC_SWITCH_OUT) {
			sfx = "OUT";
			pfx = "next";
		} else {
			sfx = "IN";
			pfx = "prev";
		}

		switch_cpu_wide = event->record.switch_cpu_wide;
		if (!switch_cpu_wide)
			return -pte_bad_packet;

		if (flags & ptsbp_compact)
			fprintf(stream, "PERF_RECORD_SWITCH_CPU_WIDE.%s  %x/%x",
				sfx, switch_cpu_wide->next_prev_pid,
				switch_cpu_wide->next_prev_tid);

		if (flags & ptsbp_verbose) {
			fprintf(stream, "PERF_RECORD_SWITCH_CPU_WIDE.%s", sfx);
			fprintf(stream, "\n  %s pid: %x", pfx,
				switch_cpu_wide->next_prev_pid);
			fprintf(stream, "\n  %s tid: %x", pfx,
				switch_cpu_wide->next_prev_tid);
		}
	}
		break;
	}

	return 0;
}

static int pt_sb_pevent_print_samples_compact(const struct pev_event *event,
					      FILE *stream)
{
	if (!event)
		return -pte_internal;

	fprintf(stream, "  {");

	if (event->sample.pid && event->sample.tid)
		fprintf(stream, " %x/%x", *event->sample.pid,
			*event->sample.tid);

	if (event->sample.time)
		fprintf(stream, " %" PRIx64, *event->sample.time);

	if (event->sample.id)
		fprintf(stream, " %" PRIx64, *event->sample.id);

	if (event->sample.cpu)
		fprintf(stream, " cpu-%x", *event->sample.cpu);

	if (event->sample.stream_id)
		fprintf(stream, " %" PRIx64, *event->sample.stream_id);

	if (event->sample.identifier)
		fprintf(stream, " %" PRIx64, *event->sample.identifier);

	fprintf(stream, " }");

	return 0;
}

static int pt_sb_pevent_print_samples_verbose(const struct pev_event *event,
					      FILE *stream)
{
	if (!event)
		return -pte_internal;

	if (event->sample.pid && event->sample.tid) {
		fprintf(stream, "\n  pid: %x", *event->sample.pid);
		fprintf(stream, "\n  tid: %x", *event->sample.tid);
	}

	if (event->sample.time)
		fprintf(stream, "\n  time: %" PRIx64, *event->sample.time);

	if (event->sample.id)
		fprintf(stream, "\n  id: %" PRIx64, *event->sample.id);

	if (event->sample.cpu)
		fprintf(stream, "\n  cpu: %x", *event->sample.cpu);

	if (event->sample.stream_id)
		fprintf(stream, "\n  stream id: %" PRIx64,
			*event->sample.stream_id);

	if (event->sample.identifier)
		fprintf(stream, "\n  identifier: %" PRIx64,
			*event->sample.identifier);

	return 0;
}

static int pt_sb_pevent_print_samples(const struct pev_event *event,
				      FILE *stream, uint32_t flags)
{
	int errcode;

	if (flags & ptsbp_compact) {
		errcode = pt_sb_pevent_print_samples_compact(event, stream);
		if (errcode < 0)
			return errcode;
	}

	if (flags & ptsbp_verbose) {
		errcode = pt_sb_pevent_print_samples_verbose(event, stream);
		if (errcode < 0)
			return errcode;
	}

	return 0;
}

static int pt_sb_pevent_print(struct pt_sb_pevent_priv *priv, FILE *stream,
			      uint32_t flags)
{
	struct pev_event *event;
	const uint8_t *pos, *begin;
	const char *filename;
	int errcode;

	if (!priv)
		return -pte_internal;

	/* We should not be called before fetching the first record. */
	pos = priv->current;
	if (!pos)
		return -pte_internal;

	begin = priv->begin;
	if (pos < begin)
		return -pte_internal;

	filename = priv->filename;
	if (!filename)
		return -pte_internal;

	event = &priv->event;

	/* Print filename and/or file offset before the actual record. */
	switch (flags & (ptsbp_filename | ptsbp_file_offset)) {
	case ptsbp_filename | ptsbp_file_offset:
		fprintf(stream, "%s:%016" PRIx64 "  ", filename,
			(uint64_t) (int64_t) (pos - begin));
		break;

	case ptsbp_filename:
		fprintf(stream, "%s  ", filename);
		break;

	case ptsbp_file_offset:
		fprintf(stream, "%016" PRIx64 "  ",
			(uint64_t) (int64_t) (pos - begin));
		break;
	}

	/* Print the timestamp if requested and available. */
	if ((flags & ptsbp_tsc) && event->sample.time)
		fprintf(stream, "%016" PRIx64 "  ", event->sample.tsc);

	/* Print the actual sideband record. */
	errcode = pt_sb_pevent_print_event(event, stream, flags);
	if (errcode < 0)
		return errcode;

	/* Print samples that were configured for the record. */
	errcode = pt_sb_pevent_print_samples(event, stream, flags);
	if (errcode < 0)
		return errcode;

	if (flags)
		fprintf(stream, "\n");

	return 0;
}

static int pt_sb_pevent_switch_contexts(struct pt_sb_session *session,
					struct pt_image **image,
					struct pt_sb_pevent_priv *priv)
{
	struct pt_sb_context *prev, *next;
	int errcode;

	if (!priv || !image)
		return -pte_internal;

	prev = priv->context;
	next = priv->next_context;
	if (!next)
		return -pte_internal;

	errcode = pt_sb_ctx_switch_to(image, session, next);
	if (errcode < 0)
		return errcode;

	priv->next_context = NULL;
	priv->context = next;

	return prev ? pt_sb_ctx_put(prev) : 0;
}

static int pt_sb_pevent_cancel_context_switch(struct pt_sb_pevent_priv *priv)
{
	struct pt_sb_context *context;

	if (!priv)
		return -pte_internal;

	context = priv->next_context;
	if (!context)
		return 0;

	priv->next_context = NULL;

	return pt_sb_ctx_put(context);
}

static int pt_sb_pevent_prepare_context_switch(struct pt_sb_pevent_priv *priv,
					       struct pt_sb_context *context)
{
	int errcode;

	if (!priv || !context)
		return -pte_internal;

	/* There's nothing to do if this switch is already pending.
	 *
	 * This could be the result of applying a cpu-wide switch-out followed
	 * by a cpu-wide switch-in without a chance to actually apply the
	 * context switch in-between.
	 */
	if (priv->next_context == context)
		return 0;

	/* This context switch overwrites any previously pending switch.
	 *
	 * We may skip context switches due to imprecise timing or due to
	 * re-synchronization after an error.
	 */
	errcode = pt_sb_pevent_cancel_context_switch(priv);
	if (errcode < 0)
		return errcode;

	/* There's nothing to do if we're switching to the current context.
	 *
	 * This could be the result of switching between threads of the same
	 * process or of applying a cpu-wide switch-out followed by a cpu-wide
	 * switch-in.
	 */
	if (priv->context == context)
		return 0;

	errcode = pt_sb_ctx_get(context);
	if (errcode < 0)
		return errcode;

	priv->next_context = context;

	return 0;
}

static int pt_sb_pevent_prepare_switch_to_pid(struct pt_sb_session *session,
					      struct pt_sb_pevent_priv *priv,
					      uint32_t pid)
{
	struct pt_sb_context *context;
	int errcode;

	context = NULL;
	errcode = pt_sb_get_context_by_pid(&context, session, pid);
	if (errcode < 0)
		return errcode;

	return pt_sb_pevent_prepare_context_switch(priv, context);
}

static int pt_sb_pevent_remove_context_for_pid(struct pt_sb_session *session,
					       uint32_t pid)
{
	struct pt_sb_context *context;
	int errcode;

	context = NULL;
	errcode = pt_sb_find_context_by_pid(&context, session, pid);
	if (errcode < 0)
		return errcode;

	if (!context)
		return 0;

	return pt_sb_remove_context(session, context);
}

static int
pt_sb_pevent_itrace_start(struct pt_sb_session *session,
			  struct pt_image **image,
			  struct pt_sb_pevent_priv *priv,
			  const struct pev_record_itrace_start *record)
{
	int errcode;

	if (!image || !record)
		return -pte_internal;

	errcode = pt_sb_pevent_prepare_switch_to_pid(session, priv,
						     record->pid);
	if (errcode < 0)
		return errcode;

	/* We may have already installed the starting context. */
	if (!priv->next_context)
		return 0;

	/* If we have not, let's not wait for a suitable event.
	 *
	 * We just started tracing so there's no reason to wait for a suitable
	 * location.
	 */
	return pt_sb_pevent_switch_contexts(session, image, priv);
}

static int pt_sb_pevent_fork(struct pt_sb_session *session,
			     const struct pev_record_fork *record)
{
	struct pt_sb_context *context, *parent;
	struct pt_image *image, *pimage;
	uint32_t ppid, pid;
	int errcode;

	if (!record)
		return -pte_internal;

	/* If this is just creating a new thread, there's nothing to do.
	 *
	 * We should already have a context for this process.  If we don't, it
	 * doesn't really help to create a new context with an empty process
	 * image at this point.
	 */
	ppid = record->ppid;
	pid = record->pid;
	if (ppid == pid)
		return 0;

	/* We're creating a new process plus the initial thread.
	 *
	 * That initial thread should get the same id as the process.
	 */
	if (pid != record->tid)
		return -pte_internal;

	/* Remove any existing context we might have for @pid.
	 *
	 * We're not removing process contexts when we get the exit event since
	 * that is sent while the process is still running inside the kernel.
	 */
	errcode = pt_sb_pevent_remove_context_for_pid(session, pid);
	if (errcode < 0)
		return errcode;

	/* Create a new context for this new process. */
	context = NULL;
	errcode = pt_sb_get_context_by_pid(&context, session, pid);
	if (errcode < 0)
		return errcode;

	/* Let's see if we also know about the parent process. */
	parent = NULL;
	errcode = pt_sb_find_context_by_pid(&parent, session, ppid);
	if (errcode < 0)
		return errcode;

	if (!parent)
		return 0;

	/* Both parent and child must have valid images. */
	pimage = pt_sb_ctx_image(parent);
	image = pt_sb_ctx_image(context);
	if (!pimage || !image)
		return -pte_internal;

	/* Initialize the child's image with its parent's. */
	return pt_image_copy(image, pimage);
}

static int pt_sb_pevent_exec(struct pt_sb_session *session,
			     struct pt_image **image,
			     struct pt_sb_pevent_priv *priv,
			     const struct pev_record_comm *record)
{
	struct pt_sb_context *context;
	uint32_t pid;
	int errcode;

	if (!record)
		return -pte_internal;

	pid = record->pid;

	/* Instead of replacing a context's image, we replace the context.
	 *
	 * This allows us to keep the old image around until we actually switch.
	 * We are likely using it at the moment.
	 */
	errcode = pt_sb_pevent_remove_context_for_pid(session, pid);
	if (errcode < 0)
		return errcode;

	/* This creates a new context and a new image.
	 *
	 * This new image will already be initialized with kernel sections but
	 * will otherwise be empty.  We will populate it later with MMAP records
	 * that follow this COMM.EXEC record.
	 */
	context = NULL;
	errcode = pt_sb_get_context_by_pid(&context, session, pid);
	if (errcode < 0)
		return errcode;

	/* If we're not maintaining a decoder, we're done. */
	if (!image)
		return 0;

	/* We replaced the previous context of @pid with @context.  Let's
	 * (prepare to) switch to the new @context.
	 *
	 * The actual context switch is postponed until we're in kernel context.
	 *
	 * It is quite likely that we are currently using the previous context
	 * we removed earlier in order to reach the location where we transition
	 * into the kernel.  In the trace, we have not yet exec'ed.
	 */
	return pt_sb_pevent_prepare_context_switch(priv, context);
}

static int pt_sb_pevent_switch(struct pt_sb_session *session,
			       struct pt_sb_pevent_priv *priv,
			       const uint32_t *pid)
{
	if (!pid)
		return -pte_bad_config;

	return pt_sb_pevent_prepare_switch_to_pid(session, priv, *pid);
}

static int
pt_sb_pevent_switch_cpu(struct pt_sb_session *session,
			struct pt_sb_pevent_priv *priv,
			const struct pev_record_switch_cpu_wide *record)
{
	if (!record)
		return -pte_internal;

	return pt_sb_pevent_prepare_switch_to_pid(session, priv,
						  record->next_prev_pid);
}

static int pt_sb_pevent_map(struct pt_sb_session *session,
			    const struct pt_sb_pevent_priv *priv, uint32_t pid,
			    const char *filename, uint64_t offset,
			    uint64_t size, uint64_t vaddr)
{
	struct pt_sb_context *context;
	const char *sysroot;
	char buffer[FILENAME_MAX];
	int errcode;

	if (!priv || !filename)
		return -pte_internal;

	/* Get the context for this process. */
	context = NULL;
	errcode = pt_sb_get_context_by_pid(&context, session, pid);
	if (errcode < 0)
		return errcode;

	/* The optional system root directory. */
	sysroot = priv->sysroot;

	/* Some filenames do not represent actual files on disk.  We handle
	 * some of those and ignore the rest.
	 */
	if (filename[0] == '[') {
		/* The [vdso] file represents the vdso that is mapped into
		 * every process.
		 *
		 * We expect the user to provide all necessary vdso flavors.
		 */
		if (strcmp(filename, "[vdso]") == 0) {
			errcode = pt_sb_pevent_find_vdso(&filename, priv,
							 context);
			if (errcode != 0)
				return pt_sb_pevent_error(session, errcode,
							  priv);
		} else
			return pt_sb_pevent_error(session, ptse_section_lost,
						  priv);


	} else if (strcmp(filename, "//anon") == 0) {
		/* Those are anonymous mappings that are, for example, used by
		 * JIT compilers to generate code in that is later executed.
		 *
		 * There is no general support for this in perf and JIT enabling
		 * is work-in-progress.
		 *
		 * We will likely fail with -pte_nomap later on.
		 */
		return pt_sb_pevent_error(session, ptse_section_lost, priv);

	} else if (strstr(filename, " (deleted)")) {
		/* The file that was mapped as meanwhile been deleted.
		 *
		 * We will likely fail with -pte_nomap later on.
		 */
		return pt_sb_pevent_error(session, ptse_section_lost, priv);

	} else if (sysroot) {
		/* Prepend the sysroot to normal files. */
		errcode = snprintf(buffer, sizeof(buffer), "%s%s", sysroot,
				   filename);
		if (errcode < 0)
			return -pte_overflow;

		filename = buffer;
	}

	errcode = pt_sb_pevent_track_abi(context, filename);
	if (errcode < 0)
		return errcode;

	return pt_sb_ctx_mmap(session, context, filename, offset, size, vaddr);
}

static int pt_sb_pevent_mmap(struct pt_sb_session *session,
			     const struct pt_sb_pevent_priv *priv,
			     const struct pev_record_mmap *record)
{
	if (!record)
		return -pte_internal;

	return pt_sb_pevent_map(session, priv, record->pid, record->filename,
				record->pgoff, record->len, record->addr);
}

static int pt_sb_pevent_mmap2(struct pt_sb_session *session,
			      const struct pt_sb_pevent_priv *priv,
			      const struct pev_record_mmap2 *record)
{
	if (!record)
		return -pte_internal;

	return pt_sb_pevent_map(session, priv, record->pid, record->filename,
				record->pgoff, record->len, record->addr);
}

static int pt_sb_pevent_aux(const struct pt_sb_session *session,
			    const struct pt_sb_pevent_priv *priv,
			    const struct pev_record_aux *record)
{
	if (!record)
		return -pte_internal;

	if (record->flags & PERF_AUX_FLAG_TRUNCATED)
		return pt_sb_pevent_error(session, ptse_trace_lost, priv);

	return 0;
}

static int pt_sb_pevent_ignore_mmap(uint16_t misc)
{
	/* We rely on the kernel core file for ring-0 decode.
	 *
	 * Both kernel and kernel modules are modified during boot and insmod
	 * respectively.  We can't decode from the respective files on disk.
	 *
	 * Ignore kernel MMAP events so we don't overwrite useful data from
	 * kcore with useless data from binary files.
	 */
	switch (misc & PERF_RECORD_MISC_CPUMODE_MASK) {
	case PERF_RECORD_MISC_KERNEL:
		return 1;

	default:
		return 0;
	}
}

static int pt_sb_pevent_apply_event_record(struct pt_sb_session *session,
					   struct pt_image **image,
					   struct pt_sb_pevent_priv *priv,
					   const struct pev_event *event)
{
	if (!event)
		return -pte_internal;

	switch (event->type) {
	default:
		/* Ignore unknown events. */
		break;

	case PERF_RECORD_ITRACE_START:
		/* Ignore trace starts from secondary sideband channels. */
		if (!image)
			break;

		return pt_sb_pevent_itrace_start(session, image, priv,
						 event->record.itrace_start);

	case PERF_RECORD_FORK:
		return pt_sb_pevent_fork(session, event->record.fork);

	case PERF_RECORD_COMM:
		/* We're only interested in COMM.EXEC events. */
		if (!(event->misc & PERF_RECORD_MISC_COMM_EXEC))
			break;

		return pt_sb_pevent_exec(session, image, priv,
					 event->record.comm);

	case PERF_RECORD_SWITCH:
		/* Ignore context switches from secondary sideband channels. */
		if (!image)
			break;

		/* Ignore switch-out events.  We wait for the switch-in. */
		if (event->misc & PERF_RECORD_MISC_SWITCH_OUT)
			break;

		return pt_sb_pevent_switch(session, priv, event->sample.pid);

	case PERF_RECORD_SWITCH_CPU_WIDE:
		/* Ignore context switches from secondary sideband channels. */
		if (!image)
			break;

		/* For switch-in events, we use the pid sample, if available.
		 *
		 * For cpu-wide switch events, not sampling pid is acceptable
		 * since we get the pid in @prev_next_pid of a switch-out event.
		 *
		 * We will use a cpu-wide switch-in event, if possible, but we
		 * should be able to do without most of the time.
		 */
		if (!(event->misc & PERF_RECORD_MISC_SWITCH_OUT)) {
			if (!event->sample.pid)
				break;

			return pt_sb_pevent_switch(session, priv,
						   event->sample.pid);
		}

		return pt_sb_pevent_switch_cpu(session, priv,
					       event->record.switch_cpu_wide);

	case PERF_RECORD_MMAP:
		/* We intentionally ignore some MMAP records. */
		if (pt_sb_pevent_ignore_mmap(event->misc))
			break;

		return pt_sb_pevent_mmap(session, priv, event->record.mmap);

	case PERF_RECORD_MMAP2:
		/* We intentionally ignore some MMAP records. */
		if (pt_sb_pevent_ignore_mmap(event->misc))
			break;

		return pt_sb_pevent_mmap2(session, priv, event->record.mmap2);

	case PERF_RECORD_LOST:
		/* Warn about losses.
		 *
		 * We put the warning into the output.  It is quite likely that
		 * we will run into a decode error shortly after (or ran into it
		 * already); this warning may help explain it.
		 */
		return pt_sb_pevent_error(session, ptse_lost, priv);

	case PERF_RECORD_AUX:
		/* Ignore trace losses from secondary sideband channels. */
		if (!image)
			break;

		return pt_sb_pevent_aux(session, priv, event->record.aux);
	}

	return 0;
}

static int ploc_from_ip(enum pt_sb_pevent_loc *loc,
			const struct pt_sb_pevent_priv *priv, uint64_t ip)
{
	if (!loc || !priv)
		return -pte_internal;

	*loc = (ip < priv->kernel_start) ? ploc_in_user : ploc_in_kernel;

	return 0;
}

static int ploc_from_suppressed_ip(enum pt_sb_pevent_loc *loc,
				   enum pt_sb_pevent_loc from)
{
	if (!loc)
		return -pte_internal;

	switch (from) {
	default:
		*loc = ploc_unknown;
		break;

	case ploc_likely_in_kernel:
	case ploc_in_kernel:
		*loc = ploc_likely_in_user;
		break;

	case ploc_likely_in_user:
	case ploc_in_user:
		*loc = ploc_likely_in_kernel;
		break;
	}

	return 0;
}

static int ploc_from_event(enum pt_sb_pevent_loc *loc,
			   const struct pt_sb_pevent_priv *priv,
			   const struct pt_event *event)
{
	if (!loc || !priv || !event)
		return -pte_internal;

	switch (event->type) {
	default:
		break;

	case ptev_enabled:
		return ploc_from_ip(loc, priv, event->variant.enabled.ip);

	case ptev_disabled:
		if (!event->ip_suppressed)
			return ploc_from_ip(loc, priv,
					    event->variant.disabled.ip);

		return ploc_from_suppressed_ip(loc, priv->location);

	case ptev_async_disabled: {
		enum pt_sb_pevent_loc fromloc;
		int errcode;

		if (!event->ip_suppressed)
			return ploc_from_ip(loc, priv,
					    event->variant.async_disabled.ip);

		errcode = ploc_from_ip(&fromloc, priv,
				       event->variant.async_disabled.at);
		if (errcode < 0)
			return errcode;

		return ploc_from_suppressed_ip(loc, fromloc);
	}

	case ptev_async_branch:
		return ploc_from_ip(loc, priv, event->variant.async_branch.to);

	case ptev_async_paging:
		if (!event->ip_suppressed)
			return ploc_from_ip(loc, priv,
					    event->variant.async_paging.ip);

		fallthrough;
	case ptev_paging:
		*loc = ploc_likely_in_kernel;
		return 0;

	case ptev_overflow:
		if (!event->ip_suppressed)
			return ploc_from_ip(loc, priv,
					    event->variant.overflow.ip);

		break;

	case ptev_exec_mode:
		if (!event->ip_suppressed)
			return ploc_from_ip(loc, priv,
					    event->variant.exec_mode.ip);

		break;

	case ptev_tsx:
		if (!event->ip_suppressed)
			return ploc_from_ip(loc, priv,
					    event->variant.tsx.ip);

		break;

	case ptev_exstop:
		if (!event->ip_suppressed)
			return ploc_from_ip(loc, priv,
					    event->variant.exstop.ip);

		break;

	case ptev_mwait:
		if (!event->ip_suppressed)
			return ploc_from_ip(loc, priv,
					    event->variant.mwait.ip);

		break;

	case ptev_ptwrite:
		if (!event->ip_suppressed)
			return ploc_from_ip(loc, priv,
					    event->variant.ptwrite.ip);

		break;

	case ptev_tick:
		if (!event->ip_suppressed)
			return ploc_from_ip(loc, priv,
					    event->variant.tick.ip);

		break;

	case ptev_tip:
		return ploc_from_ip(loc, priv, event->variant.tip.ip);

#if (LIBIPT_VERSION >= 0x201)
	case ptev_iflags:
		if (!event->ip_suppressed)
			return ploc_from_ip(loc, priv,
					    event->variant.iflags.ip);

		break;

	case ptev_interrupt:
		if (!event->ip_suppressed)
			return ploc_from_ip(loc, priv,
					    event->variant.interrupt.ip);

		break;

	case ptev_iret:
		if (!event->ip_suppressed)
			return ploc_from_ip(loc, priv,
					    event->variant.iret.ip);

		break;

	case ptev_smi:
		if (!event->ip_suppressed)
			return ploc_from_ip(loc, priv,
					    event->variant.smi.ip);

		break;

	case ptev_rsm:
		if (!event->ip_suppressed)
			return ploc_from_ip(loc, priv,
					    event->variant.rsm.ip);

		break;

	case ptev_init:
		if (!event->ip_suppressed)
			return ploc_from_ip(loc, priv,
					    event->variant.init.ip);

		break;

	case ptev_vmentry:
		if (!event->ip_suppressed)
			return ploc_from_ip(loc, priv,
					    event->variant.vmentry.ip);

		break;

	case ptev_vmexit:
		if (!event->ip_suppressed)
			return ploc_from_ip(loc, priv,
					    event->variant.vmexit.ip);

		break;

	case ptev_shutdown:
		if (!event->ip_suppressed)
			return ploc_from_ip(loc, priv,
					    event->variant.shutdown.ip);

		break;

	case ptev_uintr:
		if (!event->ip_suppressed)
			return ploc_from_ip(loc, priv,
					    event->variant.uintr.ip);

		break;

	case ptev_uiret:
		if (!event->ip_suppressed)
			return ploc_from_ip(loc, priv,
					    event->variant.uiret.ip);

		break;
#endif
	}

	*loc = ploc_unknown;
	return 0;
}

static int pt_sb_pevent_apply(struct pt_sb_session *session,
			      struct pt_image **image,
			      const struct pt_event *event,
			      struct pt_sb_pevent_priv *priv)
{
	const struct pev_event *record;
	enum pt_sb_pevent_loc oldloc;
	int errcode;

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

	/* If the current perf event record is due, apply it.
	 *
	 * We don't need to look at the actual event that provided the
	 * timestamp.  It suffices to know that time moved beyond the current
	 * perf event record.
	 *
	 * It is tempting to postpone applying the record until a suitable event
	 * but we need to ensure that records from different channels are
	 * applied in timestamp order.
	 *
	 * So we apply the record solely based on timestamps and postpone its
	 * effect until a suitable event.
	 *
	 * The last record in the trace won't be overridden and we have to take
	 * care to not apply it twice.  We need to keep it until we were able to
	 * place the last pending context switch.
	 */
	record = &priv->event;
	if ((priv->current != priv->next) &&
	    (!record->sample.time || (record->sample.tsc <= event->tsc)))
		return pt_sb_pevent_apply_event_record(session, image, priv,
						       record);

	/* We first apply all our sideband records one-by-one until we're in
	 * sync with the event.
	 *
	 * When we get here, we applied all previous sideband records.  Let's
	 * use the event to keep track of kernel vs user space and apply any
	 * postponed context switches.
	 *
	 * We preserve the previous location to detect returns from kernel to
	 * user space.
	 */
	oldloc = priv->location;
	errcode = ploc_from_event(&priv->location, priv, event);
	if (errcode < 0)
		return errcode;

	/* We postpone context switches until we reach a suitable location in
	 * the trace.  If we don't have a context switch pending, we're done.
	 */
	if (!priv->next_context) {
		/* Signal the end of the trace if the last event did not result
		 * in a postponed context switch or if that context switch had
		 * been applied at a previous event.
		 */
		if (priv->current == priv->next)
			return -pte_eos;

		return 0;
	}

	/* Apply a postponed context switch inside kernel mode.
	 *
	 * For our purposes it does not matter where exactly we are in the
	 * kernel.
	 *
	 * In order to catch the first event window right before a tracing
	 * enabled event after some time of tracing being disabled (or at the
	 * beginning of the trace), we also accept an unknown location.
	 */
	switch (oldloc) {
	case ploc_likely_in_kernel:
	case ploc_in_kernel:
	case ploc_unknown:
		return pt_sb_pevent_switch_contexts(session, image, priv);

	default:
		switch (priv->location) {
		case ploc_likely_in_kernel:
		case ploc_in_kernel:
		case ploc_unknown:
			return pt_sb_pevent_switch_contexts(session, image,
							    priv);

		default:
			break;
		}

		break;
	}

	return 0;
}

static int pt_sb_pevent_fetch_callback(struct pt_sb_session *session,
				       uint64_t *tsc, void *priv)
{
	int errcode;

	errcode = pt_sb_pevent_fetch(tsc, (struct pt_sb_pevent_priv *) priv);
	if ((errcode < 0) && (errcode != -pte_eos))
		pt_sb_pevent_error(session, errcode,
				   (struct pt_sb_pevent_priv *) priv);

	return errcode;
}

static int pt_sb_pevent_print_callback(struct pt_sb_session *session,
				       FILE *stream, uint32_t flags, void *priv)
{
	int errcode;

	errcode = pt_sb_pevent_print((struct pt_sb_pevent_priv *) priv, stream,
				     flags);
	if (errcode < 0)
		return pt_sb_pevent_error(session, errcode,
					  (struct pt_sb_pevent_priv *) priv);

	return 0;
}

static int pt_sb_pevent_apply_callback(struct pt_sb_session *session,
				       struct pt_image **image,
				       const struct pt_event *event, void *priv)
{
	int errcode;

	errcode = pt_sb_pevent_apply(session, image, event,
				     (struct pt_sb_pevent_priv *) priv);
	if ((errcode < 0) && (errcode != -pte_eos))
		return pt_sb_pevent_error(session, errcode,
					  (struct pt_sb_pevent_priv *) priv);

	return errcode;
}

int pt_sb_alloc_pevent_decoder(struct pt_sb_session *session,
			       const struct pt_sb_pevent_config *pev)
{
	struct pt_sb_decoder_config config;
	struct pt_sb_pevent_priv *priv;
	int errcode;

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

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

	errcode = pt_sb_pevent_init(priv, pev);
	if (errcode < 0) {
		free(priv);
		return errcode;
	}

	memset(&config, 0, sizeof(config));
	config.fetch = pt_sb_pevent_fetch_callback;
	config.apply = pt_sb_pevent_apply_callback;
	config.print = pt_sb_pevent_print_callback;
	config.dtor = pt_sb_pevent_dtor;
	config.priv = priv;
	config.primary = pev->primary;

	errcode = pt_sb_alloc_decoder(session, &config);
	if (errcode < 0)
		free(priv);

	return errcode;
}

#endif /* FEATURE_PEVENT */
