/*
 * Copyright (C) 2013-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 "errcode.h"
#include "parse.h"
#include "util.h"
#include "pt_compiler.h"

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <limits.h>

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


static const char pt_suffix[] = ".pt";
static const char exp_suffix[] = ".exp";

#if defined(FEATURE_SIDEBAND)
static const char sb_suffix[] = ".sb";
#endif

enum {
	pd_len = 1024
};

#if defined(FEATURE_SIDEBAND)

static void sb_rename_file(struct sb_file *sb)
{
	char filename[FILENAME_MAX];
	int len;

	/* We encode the configuration in the sideband filename. */
	switch (sb->format) {
	case sbf_raw:
		len = snprintf(filename, sizeof(filename), "%s", sb->name);
		if ((len < 0) || (sizeof(filename) <= (size_t) len)) {
			fprintf(stderr, "error renaming %s.\n", sb->name);
			return;
		}
		break;

#if defined(FEATURE_PEVENT)
	case sbf_pevent: {
		const struct pev_config *config;
		size_t base_len, ext_len, suffix_len, total_len;
		int errcode, printed;
		char extension[256];

		config = &sb->variant.pevent.config;

		printed = snprintf(extension, sizeof(extension),
				   ",sample-type=0x%" PRIx64 ",time-zero=0x%"
				   PRIx64 ",time-shift=0x%u" ",time-mult=0x%u",
				   config->sample_type, config->time_zero,
				   config->time_shift, config->time_mult);
		if (printed < 0) {
			fprintf(stderr, "error renaming %s.\n", sb->name);
			return;
		}

		ext_len = (size_t) printed;

		suffix_len = sizeof(sb_suffix) - 1;
		base_len = strnlen(sb->name, sizeof(filename));
		if (base_len < suffix_len) {
			fprintf(stderr, "error renaming %s.\n", sb->name);
			return;
		}

		base_len -= suffix_len;

		total_len = base_len + ext_len + suffix_len + 1;
		if (sizeof(filename) <= total_len) {
			fprintf(stderr, "warning: %s could not be renamed.\n",
				sb->name);
			return;
		}

		if (INT_MAX < base_len) {
			fprintf(stderr, "error renaming %s.\n", sb->name);
			return;
		}

		printed = snprintf(filename, sizeof(filename), "%.*s%s%s",
				   (int) base_len, sb->name, extension,
				   sb_suffix);
		if ((printed < 0) || (sizeof(filename) <= (size_t) printed)) {
			fprintf(stderr, "error renaming %s.\n", sb->name);
			return;
		}

		errno = 0;
		errcode = rename(sb->name, filename);
		if (errcode < 0)
			fprintf(stderr, "error renaming %s: %s.\n",
				sb->name, strerror(errno));
	}
		break;
#endif /* defined(FEATURE_PEVENT) */
	}

	/* Print the name of the sideband file for test.bash. */
	printf("%s ", filename);
}

#endif /* defined(FEATURE_SIDEBAND) */

/* Deallocates the memory used by @p, closes all files, clears and
 * zeroes the fields.
 */
static void p_free(struct parser *p)
{
	if (!p)
		return;

	yasm_free(p->y);
	pd_free(p->pd);
	l_free(p->pt_labels);
	free(p->ptfilename);

#if defined(FEATURE_SIDEBAND)
	{
		struct sb_filelist *sb;

		sb = p->sbfiles;
		while (sb) {
			struct sb_filelist *trash;

			trash = sb;
			sb = sb->next;

			fclose(trash->sbfile.file);

			sb_rename_file(&trash->sbfile);

			free(trash->sbfile.name);
			free(trash);
		}
	}
#endif /* defined(FEATURE_SIDEBAND) */

	free(p);
}

/* Initializes @p with @pttfile and @conf.
 *
 * Returns 0 on success; a negative enum errcode otherwise.
 * Returns -err_internal if @p is the NULL pointer.
 */
static struct parser *p_alloc(const char *pttfile, const struct pt_config *conf)
{
	size_t n, size;
	struct parser *p;
	int len;

	if (!conf)
		return NULL;

	if (!pttfile)
		return NULL;

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

	p->y = yasm_alloc(pttfile);
	if (!p->y)
		goto error;

	n = strnlen(p->y->fileroot, FILENAME_MAX - sizeof(pt_suffix));
	if ((FILENAME_MAX - sizeof(pt_suffix)) <= n)
		goto error;

	size = n + sizeof(pt_suffix);

	p->ptfilename = malloc(size);
	if (!p->ptfilename)
		goto error;

	len = snprintf(p->ptfilename, size, "%s%s", p->y->fileroot, pt_suffix);
	if ((len < 0) || ((size_t) len != (size - 1)))
		goto error;

	p->pd = pd_alloc(pd_len);
	if (!p->pd)
		goto error;

	p->pt_labels = l_alloc();
	if (!p->pt_labels)
		goto error;

	p->conf = conf;

#if defined(FEATURE_SIDEBAND)
	p->sbfiles = NULL;
	p->current_sbfile = NULL;
#endif

	return p;

error:
	p_free(p);
	return NULL;
}

/* Generates an .exp filename following the scheme:
 *	<fileroot>[-<extra>][-cpu_<f>_<m>[_<s>]].exp
 */
static char *expfilename(struct parser *p, const char *extra)
{
	char filename[FILENAME_MAX], cpuext[64], *pfname;
	int len;

	memset(filename, 0, sizeof(filename));
	memset(cpuext, 0, sizeof(cpuext));

	if (p->conf->cpu.vendor != pcv_unknown) {
		struct pt_cpu cpu;

		cpu = p->conf->cpu;
		if (cpu.stepping)
			len = snprintf(cpuext, sizeof(cpuext),
				       "-cpu_%" PRIu16 "_%" PRIu8 "_%" PRIu8,
				       cpu.family, cpu.model, cpu.stepping);
		else
			len = snprintf(cpuext, sizeof(cpuext),
				       "-cpu_%" PRIu16 "_%" PRIu8, cpu.family,
				       cpu.model);

		if ((len < 0) || (sizeof(cpuext) <= (size_t) len))
			return NULL;
	}

	if (extra && *extra)
		len = snprintf(filename, sizeof(filename), "%s-%s%s%s",
			       p->y->fileroot, extra, cpuext, exp_suffix);
	else
		len = snprintf(filename, sizeof(filename), "%s-%s%s",
			       p->y->fileroot, cpuext, exp_suffix);

	if ((len < 0) || (sizeof(filename) <= (size_t) len))
		return NULL;

	pfname = malloc((size_t) len + 1);
	if (!pfname)
		return NULL;

	pfname[len] = 0;

	return memcpy(pfname, filename, (size_t) len);
}

/* Returns true if @c is part of a label; false otherwise.  */
static int islabelchar(int c)
{
	if (isalnum(c))
		return 1;

	switch (c) {
	case '_':
		return 1;
	}

	return 0;
}

/* Generates the content of the .exp file by printing all lines with
 * everything up to and including the first comment semicolon removed.
 *
 * Returns 0 on success; a negative enum errcode otherwise.
 * Returns -err_internal if @p is the NULL pointer.
 * Returns -err_file_write if the .exp file could not be fully written.
 */
static int p_gen_expfile(struct parser *p)
{
	int errcode;
	char s[1024];
	struct pt_directive *pd;
	char *filename;
	FILE *f;
	size_t slen;

	if (bug_on(!p))
		return -err_internal;

	pd = p->pd;

	/* the directive in the current line must be the .exp directive.  */
	errcode = yasm_pd_parse(p->y, pd);
	if (bug_on(errcode < 0))
		return -err_internal;

	if (bug_on(strcmp(pd->name, ".exp") != 0))
		return -err_internal;

	filename = expfilename(p, pd->payload);
	if (!filename)
		return -err_no_mem;
	f = fopen(filename, "w");
	if (!f) {
		free(filename);
		return -err_file_open;
	}

	for (;;) {
		int i;
		char *line, *comment, *end;

		errcode = yasm_next_line(p->y, s, sizeof(s));
		if (errcode < 0)
			break;

		slen = strnlen(s, sizeof(s));
		if (sizeof(s) <= slen) {
			errcode = -err_internal;
			break;
		}
		end = &s[slen];

		errcode = yasm_pd_parse(p->y, pd);
		if (errcode < 0 && errcode != -err_no_directive)
			break;

		if (errcode == 0 && strcmp(pd->name, ".exp") == 0) {
			fclose(f);
			printf("%s ", filename);
			free(filename);
			filename = expfilename(p, pd->payload);
			if (!filename)
				return -err_no_mem;
			f = fopen(filename, "w");
			if (!f) {
				free(filename);
				return -err_file_open;
			}
			continue;
		}

		line = strchr(s, ';');
		if (!line)
			continue;

		line += 1;

		comment = strchr(line, '#');
		if (comment) {
			end = comment;
			*comment = '\0';
		}
		/* remove trailing spaces.  */
		for (end -= 1; line <= end && isspace(*end); --end)
			*end = '\0';

		for (;;) {
			char *tmp, label[256];
			uint64_t addr;
			int zero_padding, qmark_padding, qmark_size, status;

			zero_padding = 0;
			qmark_padding = 0;
			qmark_size = 0;
			status = 0;

			/* find the label character in the string.
			 * if there is no label character, we just print
			 * the rest of the line and end.
			 */
			tmp = strchr(line, '%');
			if (!tmp) {
				if (fprintf(f, "%s", line) < 0) {
					errcode = -err_file_write;
					goto error;
				}
				break;
			}

			/* make the label character a null byte and
			 * print the first portion, which does not
			 * belong to the label into the file.
			 */
			*tmp = '\0';
			if (fprintf(f, "%s", line) < 0) {
				errcode = -err_file_write;
				goto error;
			}

			/* test if there is a valid label name after the %.  */
			line = tmp+1;
			if (*line == '\0' || isspace(*line)) {
				errcode = -err_no_label;
				goto error;
			}

			/* check if zero padding is requested.  */
			if (*line == '0') {
				zero_padding = 1;
				line += 1;
			}
			/* check if ? padding is requested.  */
			else if (*line == '?') {
				qmark_padding = 1;
				zero_padding = 1;
				qmark_size = 0;
				line += 1;
			}

			/* advance i to the first non alphanumeric
			 * character. all characters everything from
			 * line[0] to line[i-1] belongs to the label
			 * name.
			 */
			for (i = 0; islabelchar(line[i]); i++)
				;

			if (l_max <= i) {
				errcode = -err_label_name;
				goto error;
			}
			strncpy(label, line, (size_t) i);
			label[i] = '\0';

			/* advance to next character.  */
			line = &line[i];

			/* lookup the label name and print it to the
			 * output file.
			 */
			errcode = yasm_lookup_label(p->y, &addr, label);
			if (errcode < 0) {
				errcode = l_lookup(p->pt_labels, &addr, label);
				if (errcode < 0)
					goto error;

				if (zero_padding)
					status = fprintf(f, "%016" PRIx64, addr);
				else
					status = fprintf(f, "%" PRIx64, addr);

				if (status < 0) {
					errcode = -err_file_write;
					goto error;
				}

				continue;
			}

			/* check if masking is requested.  */
			if (*line == '.') {
				char *endptr;
				unsigned long int n;

				line += 1;

				n = strtoul(line, &endptr, 0);
				/* check if strtol made progress and
				 * stops on a space or null byte.
				 * otherwise the int could not be
				 * parsed.
				 */
				if (line == endptr ||
				    (*endptr != '\0' && !isspace(*endptr)
				     && !ispunct(*endptr))) {
					errcode = -err_parse_int;
					goto error;
				}
				if (8 < n) {
					errcode = -err_parse_int;
					goto error;
				}

				addr &= (1ull << (n << 3)) - 1ull;
				line = endptr;

				qmark_size = (int) (8 - n);
			}

			if (qmark_padding) {
				for (i = 0; i < qmark_size; ++i) {
					status = fprintf(f, "??");
					if (status < 0) {
						errcode = -err_file_write;
						goto error;
					}
				}

				for (; i < 8; ++i) {
					uint8_t byte;

					byte = (uint8_t)(addr >> ((7 - i) * 8));

					status = fprintf(f, "%02" PRIx8, byte);
					if (status < 0) {
						errcode = -err_file_write;
						goto error;
					}
				}
			} else if (zero_padding)
				status = fprintf(f, "%016" PRIx64, addr);
			else
				status = fprintf(f, "%" PRIx64, addr);

			if (status < 0) {
				errcode = -err_file_write;
				goto error;
			}

		}

		if (fprintf(f, "\n") < 0) {
			errcode = -err_file_write;
			goto error;
		}
	}

error:

	fclose(f);
	if (errcode < 0 && errcode != -err_out_of_range) {
		fprintf(stderr, "fatal: error generating %s:\n", filename);
		yasm_print_err(p->y, "", errcode);
	} else
		printf("%s ", filename);
	free(filename);

	/* If there are no lines left, we are done.  */
	if (errcode == -err_out_of_range)
		return 0;

	return errcode;
}

static void p_close_files(struct parser *p)
{
	if (p->ptfile) {
		fclose(p->ptfile);
		p->ptfile = NULL;
	}
}

static int p_open_files(struct parser *p)
{
	p->ptfile = fopen(p->ptfilename, "wb");
	if (!p->ptfile) {
		fprintf(stderr, "open %s failed\n", p->ptfilename);
		goto error;
	}
	return 0;

error:
	p_close_files(p);
	return -err_file_open;
}

static int report_lib_error(struct parser *p, const char *message, int errcode)
{
	char buffer[128];

	if (bug_on(!p))
		return err_internal;

	snprintf(buffer, sizeof(buffer), "%s: %s", message,
		 pt_errstr(pt_errcode(errcode)));

	yasm_print_err(p->y, buffer, -err_pt_lib);

	return -err_pt_lib;
}

/* Parse any amount of whitespace from @pinput, including none.
 *
 * We want to stay within one source line so we only parse spaces and
 * horizontal tabs.
 *
 * Returns zero on success and updates @input.
 * Returns a negative integer on error.
 */
static int parse_whitespace(const char **pinput)
{
	const char *input;

	if (!pinput)
		return -err_internal;

	input = *pinput;
	if (!input)
		return -err_internal;

	while ((*input == ' ') || (*input == '\t'))
		input += 1;

	*pinput = input;
	return 0;
}

/* Parse @size bytes of a literal @token in @input.
 *
 * Returns zero on success and updates @input.
 * Returns a positive integer on failure.
 * Returns a negative integer on error.
 */
static int parse_token_aux(const char **pinput, const char *token, size_t size)
{
	const char *input;
	int status;

	if (!pinput)
		return -err_internal;

	input = *pinput;
	if (!input)
		return -err_internal;

	if (!token || !size)
		return -err_internal;

	status = parse_whitespace(&input);
	if (status < 0)
		return status;

	if (strncmp(input, token, size) != 0)
		return 1;

	input += size;
	*pinput = input;
	return 0;
}

/* Parse a string literal @token in @input. */
#define parse_token(input, token)				\
	parse_token_aux(input, token, sizeof(token) - 1)

/* Parse an @size-bit base-@base unsigned integer in @input.
 *
 * If an integer number can be parsed from @input, diagnoses exceeding the
 * expected range with an error code return.
 *
 * Returns zero on success and updates @input.
 * Returns a positive integer if @input does not start with an integer.
 * Returns a negative integer on error.
 */
static int parse_uint(const char **pinput, unsigned long long *puint,
		      uint8_t size, int base)
{
	unsigned long long uint;
	const char *input;
	char *end;

	if (!pinput)
		return -err_internal;

	input = *pinput;
	if (!input)
		return -err_internal;

	if (!puint || !size)
		return -err_internal;

	if (size > 64)
		return -err_internal;

	errno = 0;
	uint = strtoull(input, &end, base);
	if (input == end)
		return 1;

	if (errno == EINVAL)
		return -err_internal;

	if (errno == ERANGE)
		return -err_parse_int_too_big;

	if ((size < 64) && ((uint >> size) != 0))
		return -err_parse_int_too_big;

	*puint = uint;
	*pinput = end;
	return 0;
}

static int parse_uint_8(const char **pinput, uint8_t *uint)
{
	unsigned long long tmp;
	int status;

	status = parse_uint(pinput, &tmp, 8, 0);
	if (status != 0)
		return status;

	*uint = (uint8_t) tmp;
	return 0;
}

static int parse_uint_16(const char **pinput, uint16_t *uint)
{
	unsigned long long tmp;
	int status;

	status = parse_uint(pinput, &tmp, 16, 0);
	if (status != 0)
		return status;

	*uint = (uint16_t) tmp;
	return 0;
}

static int parse_uint_64(const char **pinput, uint64_t *uint)
{
	unsigned long long tmp;
	int status;

	status = parse_uint(pinput, &tmp, 64, 0);
	if (status != 0)
		return status;

	*uint = (uint64_t) tmp;
	return 0;
}

static int parse_mwait(uint32_t *hints, uint32_t *ext, char *payload)
{
	int errcode;

	if (bug_on(!hints || !ext))
		return -err_internal;

	payload = strtok(payload, ",");
	if (!payload || *payload == '\0')
		return -err_parse_no_args;

	errcode = str_to_uint32(payload, hints, 0);
	if (errcode < 0)
		return errcode;

	payload = strtok(NULL, " ,");
	if (!payload)
		return -err_parse_no_args;

	errcode = str_to_uint32(payload, ext, 0);
	if (errcode < 0)
		return errcode;

	/* no more tokens left.  */
	payload = strtok(NULL, " ");
	if (payload)
		return -err_parse_trailing_tokens;

	return 0;
}

static int parse_c_state(uint8_t *state, uint8_t *sub_state, const char *input)
{
	unsigned int maj, min;
	int matches;

	if (!input)
		return -err_parse_no_args;

	maj = 0;
	min = 0;
	matches = sscanf(input, " c%u.%u", &maj, &min);
	switch (matches) {
	default:
		return -err_parse;

	case 0:
		return -err_parse_no_args;

	case 2:
		if (!sub_state)
			return -err_parse_c_state_sub;

		if (0xf <= min)
			return -err_parse_c_state_invalid;

		fallthrough;
	case 1:
		if (!state)
			return -err_internal;

		if (0xf <= maj)
			return -err_parse_c_state_invalid;

		break;
	}

	*state = (uint8_t) (((int) maj - 1) & 0xf);
	if (sub_state)
		*sub_state = (uint8_t) (((int) min - 1) & 0xf);

	return 0;
}

/* Parse a mode.exec execution mode in @input into @packet.
 *
 * Returns zero on success and updates @input.
 * Returns a positive integer on failure.
 * Returns a negative integer on error.
 */
static int parse_exec_mode(const char **input,
			   struct pt_packet_mode_exec *packet)
{
	int status;

	if (!packet)
		return -err_internal;

	status = parse_token(input, "64bit");
	if (status <= 0) {
		if (status < 0)
			return status;

		packet->csl = 1;
		packet->csd = 0;

		return status;
	}

	status = parse_token(input, "32bit");
	if (status <= 0) {
		if (status < 0)
			return status;

		packet->csl = 0;
		packet->csd = 1;

		return status;
	}

	status = parse_token(input, "16bit");
	if (status <= 0) {
		if (status < 0)
			return status;

		packet->csl = 0;
		packet->csd = 0;

		return status;
	}

	return 1;
}

/* Parse mode.exec arguments in @input into @packet.
 *
 * Returns zero on success and updates @input.
 * Returns a positive integer on failure.
 * Returns a negative integer on error.
 */
static int parse_mode_exec(const char **input, const struct parser *p,
			   struct pt_packet_mode_exec *packet)
{
	int status;

	if (!p || !packet)
		return -err_internal;

	status = parse_exec_mode(input, packet);
	if (status != 0) {
		if (status < 0)
			return status;

		status = yasm_print_err(p->y, "mode.exec: bad argument, "
					"expected \"16bit\", \"64bit\" or "
					"\"32bit\"", -err_parse);
		if (status < 0)
			return status;

		return 1;
	}

	status = parse_token(input, ",");
	if (status != 0) {
		if (status < 0)
			return status;

		packet->iflag = 0;
		return 0;
	}

	status = parse_token(input, "if");
	if (status != 0) {
		if (status < 0)
			return status;

		status = yasm_print_err(p->y, "mode.exec: bad argument, "
					"expected \"if\"", -err_parse);
		if (status < 0)
			return status;

		return 1;
	}

	packet->iflag = 1;
	return 0;
}

/* Parse cfe arguments in @input into @packet.
 *
 * Returns zero on success and updates @input.
 * Returns a positive integer on failure.
 * Returns a negative integer on error.
 */
static int parse_cfe(const char **input, const struct parser *p,
		     struct pt_packet_cfe *packet)
{
	uint8_t type;
	int status;

	if (!p || !packet)
		return -err_internal;

	status = parse_uint_8(input, &type);
	if (status != 0) {
		if (status < 0)
			return status;

		status = yasm_print_err(p->y, "cfe: bad argument, expected "
					"8-bit unsigned integer 'type'",
					-err_parse);
		if (status < 0)
			return status;

		return 1;
	}

	packet->type = (enum pt_cfe_type) type;

	status = parse_token(input, ":");
	if (status != 0) {
		if (status < 0)
			return status;

		switch (packet->type) {
		case pt_cfe_intr:
		case pt_cfe_sipi:
		case pt_cfe_vmexit_intr:
		case pt_cfe_uintr:
		case pt_cfe_swintr:
			status = yasm_print_err(p->y, "cfe: type needs "
						"'vector' argument",
						-err_parse);
			if (status < 0)
				return status;

			return 1;

		case pt_cfe_iret:
		case pt_cfe_smi:
		case pt_cfe_rsm:
		case pt_cfe_init:
		case pt_cfe_vmentry:
		case pt_cfe_vmexit:
		case pt_cfe_shutdown:
		case pt_cfe_uiret:
		case pt_cfe_syscall:
			packet->vector = 0;
			break;
		}
	} else {
		status = parse_uint_8(input, &packet->vector);
		if (status != 0) {
			if (status < 0)
				return status;

			status = yasm_print_err(p->y, "cfe: bad argument, "
						"expected  8-bit unsigned "
						"integer 'vector'", -err_parse);
			if (status < 0)
				return status;

			return 1;
		}
	}

	status = parse_token(input, ",");
	if (status != 0) {
		if (status < 0)
			return status;

		packet->ip = 0;
		return 0;
	}

	status = parse_token(input, "ip");
	if (status != 0) {
		if (status < 0)
			return status;

		status = yasm_print_err(p->y, "cfe: bad argument, expected"
					"'ip' keyword", -err_parse);
		if (status < 0)
			return status;

		return 1;
	}

	packet->ip = 1;
	return 0;
}

/* Parse evd arguments in @input into @packet.
 *
 * Returns zero on success and updates @input.
 * Returns a positive integer on failure.
 * Returns a negative integer on error.
 */
static int parse_evd(const char **input, const struct parser *p,
		     struct pt_packet_evd *packet)
{
	uint8_t type;
	int status;

	if (!p || !packet)
		return -err_internal;

	status = parse_uint_8(input, &type);
	if (status != 0) {
		if (status < 0)
			return status;

		status = yasm_print_err(p->y, "evd: bad argument, expected "
					"8-bit unsigned integer 'type'",
					-err_parse);
		if (status < 0)
			return status;

		return 1;
	}

	packet->type = (enum pt_evd_type) type;

	status = parse_token(input, ":");
	if (status != 0) {
		if (status < 0)
			return status;

		status = yasm_print_err(p->y, "evd: bad argument, expected "
					"':' separator", -err_parse);
		if (status < 0)
			return status;

		return 1;
	}

	status = parse_uint_64(input, &packet->payload);
	if (status != 0) {
		if (status < 0)
			return status;

		status = yasm_print_err(p->y, "evd: bad argument, expected "
					"64-bit unsigned integer 'payload'",
					-err_parse);
		if (status < 0)
			return status;

		return 1;
	}

	return 0;
}

/* Parse trig arguments in @input into @packet.
 *
 * Returns zero on success and updates @input.
 * Returns a positive integer on failure.
 * Returns a negative integer on error.
 */
static int parse_trig(const char **input, const struct parser *p,
		      struct pt_packet_trig *packet)
{
	int status;

	if (!p || !packet)
		return -err_internal;

	memset(packet, 0, sizeof(*packet));

	status = parse_uint_8(input, &packet->trbv);
	if (status != 0) {
		if (status < 0)
			return status;

		status = yasm_print_err(p->y, "trig: bad argument, expected "
					"8-bit unsigned integer 'trbv'",
					-err_parse);
		if (status < 0)
			return status;

		return 1;
	}

	for (;;) {
		status = parse_token(input, ",");
		if (status != 0) {
			if (status < 0)
				return status;

			break;
		}

		status = parse_token(input, "ip");
		if (status <= 0) {
			if (status < 0)
				return status;

			packet->ip = 1;
			continue;
		}

		status = parse_token(input, "mult");
		if (status <= 0) {
			if (status < 0)
				return status;

			packet->mult = 1;
			continue;
		}

		status = parse_token(input, "icnt");
		if (status <= 0) {
			if (status < 0)
				return status;

			packet->icntv = 1;

			status = parse_token(input, ":");
			if (status != 0) {
				if (status < 0)
					return status;

				status = yasm_print_err(p->y, "trig: expected "
							"':' after 'icnt'",
							-err_parse);
				if (status < 0)
					return status;

				return 1;
			}

			status = parse_uint_16(input, &packet->icnt);
			if (status != 0) {
				if (status < 0)
					return status;

				status = yasm_print_err(p->y, "trig: expected "
							"16-bit uint 'icnt'",
							-err_parse);
				if (status < 0)
					return status;

				return 1;
			}

			continue;
		}
	}

	return 0;
}

static int pt_raw(struct parser *p, struct pt_encoder *e,
		  const void *buffer, size_t size)
{
	const struct pt_config *conf;
	uint8_t *begin, *end, *pos, *start;
	uint64_t offset;
	int errcode;

	if (bug_on(!p) || bug_on(!e))
		return -err_internal;

	conf = p->conf;
	if (bug_on(!conf))
		return -err_internal;

	errcode = pt_enc_get_offset(e, &offset);
	if (errcode < 0)
		return report_lib_error(p, "error getting encoder offset",
					errcode);

	begin = conf->begin;
	end = conf->end;
	pos = begin + offset;
	if (pos < begin || end <= pos)
		return err_internal;

	start = pos;
	pos += size;
	if (pos < begin || end <= pos) {
		yasm_print_err(p->y, "buffer full", -err_no_mem);
		return -err_no_mem;
	}

	memcpy(start, buffer, size);

	errcode = pt_enc_sync_set(e, offset + size);
	if (errcode < 0)
		return report_lib_error(p, "error setting encoder offset",
					errcode);

	p->pt_bytes_written += (int) size;
	return (int) size;
}

static int pt_raw_8(struct parser *p, struct pt_encoder *e, uint8_t value)
{
	return pt_raw(p, e, &value, sizeof(value));
}

static int pt_raw_16(struct parser *p, struct pt_encoder *e, uint16_t value)
{
	return pt_raw(p, e, &value, sizeof(value));
}

static int pt_raw_32(struct parser *p, struct pt_encoder *e, uint32_t value)
{
	return pt_raw(p, e, &value, sizeof(value));
}

static int pt_raw_64(struct parser *p, struct pt_encoder *e, uint64_t value)
{
	return pt_raw(p, e, &value, sizeof(value));
}

/* Processes the current directive.
 * If the encoder returns an error, a message including current file and
 * line number together with the pt error string is printed on stderr.
 *
 * Returns 0 on success; a negative enum errcode otherwise.
 * Returns -err_internal if @p or @e is the NULL pointer.
 * Returns -err_pt_lib if the pt encoder returned an error.
 * Returns -err_parse if a general parsing error was encountered.
 * Returns -err_parse_unknown_directive if there was an unknown pt directive.
 */
static int p_process_pt(struct parser *p, struct pt_encoder *e)
{
	struct pt_directive *pd;
	struct pt_packet packet;
	char *directive, *payload;
	int bytes_written, errcode;

	if (bug_on(!p))
		return -err_internal;

	if (bug_on(!e))
		return -err_internal;

	pd = p->pd;
	if (!pd)
		return -err_internal;

	directive = pd->name;
	payload = pd->payload;

	if (strcmp(directive, "psb") == 0) {
		errcode = parse_empty(payload);
		if (errcode < 0) {
			yasm_print_err(p->y, "psb: parsing failed", errcode);
			return errcode;
		}
		packet.type = ppt_psb;
	} else if (strcmp(directive, "psbend") == 0) {
		errcode = parse_empty(payload);
		if (errcode < 0) {
			yasm_print_err(p->y, "psbend: parsing failed", errcode);
			return errcode;
		}
		packet.type = ppt_psbend;
	} else if (strcmp(directive, "pad") == 0) {
		errcode = parse_empty(payload);
		if (errcode < 0) {
			yasm_print_err(p->y, "pad: parsing failed", errcode);
			return errcode;
		}
		packet.type = ppt_pad;
	} else if (strcmp(directive, "ovf") == 0) {
		errcode = parse_empty(payload);
		if (errcode < 0) {
			yasm_print_err(p->y, "ovf: parsing failed", errcode);
			return errcode;
		}
		packet.type = ppt_ovf;
	} else if (strcmp(directive, "stop") == 0) {
		errcode = parse_empty(payload);
		if (errcode < 0) {
			yasm_print_err(p->y, "stop: parsing failed", errcode);
			return errcode;
		}
		packet.type = ppt_stop;
	} else if (strcmp(directive, "tnt") == 0) {
		errcode = parse_tnt(&packet.payload.tnt.payload,
				    &packet.payload.tnt.bit_size, payload);
		if (errcode < 0) {
			yasm_print_err(p->y, "tnt: parsing failed", errcode);
			return errcode;
		}
		packet.type = ppt_tnt_8;
	} else if (strcmp(directive, "tnt64") == 0) {
		errcode = parse_tnt(&packet.payload.tnt.payload,
				    &packet.payload.tnt.bit_size, payload);
		if (errcode < 0) {
			yasm_print_err(p->y, "tnt64: parsing failed", errcode);
			return errcode;
		}
		packet.type = ppt_tnt_64;
	} else if (strcmp(directive, "tip") == 0) {
		errcode = parse_ip(p, &packet.payload.ip.ip,
				   &packet.payload.ip.ipc, payload);
		if (errcode < 0) {
			yasm_print_err(p->y, "tip: parsing failed", errcode);
			return errcode;
		}
		packet.type = ppt_tip;
	} else if (strcmp(directive, "tip.pge") == 0) {
		errcode = parse_ip(p, &packet.payload.ip.ip,
				   &packet.payload.ip.ipc, payload);
		if (errcode < 0) {
			yasm_print_err(p->y, "tip.pge: parsing failed",
				       errcode);
			return errcode;
		}
		packet.type = ppt_tip_pge;
	} else if (strcmp(directive, "tip.pgd") == 0) {
		errcode = parse_ip(p, &packet.payload.ip.ip,
				   &packet.payload.ip.ipc, payload);
		if (errcode < 0) {
			yasm_print_err(p->y, "tip.pgd: parsing failed",
				       errcode);
			return errcode;
		}
		packet.type = ppt_tip_pgd;
	} else if (strcmp(directive, "fup") == 0) {
		errcode = parse_ip(p, &packet.payload.ip.ip,
				   &packet.payload.ip.ipc, payload);
		if (errcode < 0) {
			yasm_print_err(p->y, "fup: parsing failed", errcode);
			return errcode;
		}
		packet.type = ppt_fup;
	} else if (strcmp(directive, "mode.exec") == 0) {
		const char *cpl;

		cpl = (const char *) payload;
		errcode = parse_mode_exec(&cpl, p,
					  &packet.payload.mode.bits.exec);
		if (errcode != 0) {
			if (errcode < 0)
				yasm_print_err(p->y,
					       "mode.exec: parsing failed",
					       errcode);
			else
				errcode = -err_parse;

			return errcode;
		}

		packet.payload.mode.leaf = pt_mol_exec;
		packet.type = ppt_mode;
	} else if (strcmp(directive, "mode.tsx") == 0) {
		if (strcmp(payload, "begin") == 0) {
			packet.payload.mode.bits.tsx.intx = 1;
			packet.payload.mode.bits.tsx.abrt = 0;
		} else if (strcmp(payload, "abort") == 0) {
			packet.payload.mode.bits.tsx.intx = 0;
			packet.payload.mode.bits.tsx.abrt = 1;
		} else if (strcmp(payload, "commit") == 0) {
			packet.payload.mode.bits.tsx.intx = 0;
			packet.payload.mode.bits.tsx.abrt = 0;
		} else {
			errcode = yasm_print_err(p->y,
						 "mode.tsx: argument must be one of \"begin\", \"abort\" or \"commit\"",
						 -err_parse);
			return errcode;
		}
		packet.payload.mode.leaf = pt_mol_tsx;
		packet.type = ppt_mode;
	} else if (strcmp(directive, "pip") == 0) {
		const char *modifier;

		errcode = parse_uint64(&packet.payload.pip.cr3, payload);
		if (errcode < 0) {
			yasm_print_err(p->y, "pip: parsing failed", errcode);
			return errcode;
		}
		packet.type = ppt_pip;
		packet.payload.pip.nr = 0;

		modifier = strtok(NULL, " ,");
		if (modifier) {
			if (strcmp(modifier, "nr") == 0)
				packet.payload.pip.nr = 1;
			else {
				yasm_print_err(p->y, "pip: parsing failed",
					       -err_parse_trailing_tokens);
				return errcode;
			}
		}
	} else if (strcmp(directive, "tsc") == 0) {
		errcode = parse_uint64(&packet.payload.tsc.tsc, payload);
		if (errcode < 0) {
			yasm_print_err(p->y, "tsc: parsing failed", errcode);
			return errcode;
		}
		packet.type = ppt_tsc;
	} else if (strcmp(directive, "cbr") == 0) {
		errcode = parse_uint8(&packet.payload.cbr.ratio, payload);
		if (errcode < 0) {
			yasm_print_err(p->y, "cbr: parsing cbr failed",
				       errcode);
			return errcode;
		}
		packet.type = ppt_cbr;
	} else if (strcmp(directive, "tma") == 0) {
		errcode = parse_tma(&packet.payload.tma.ctc,
				    &packet.payload.tma.fc, payload);
		if (errcode < 0) {
			yasm_print_err(p->y, "tma: parsing tma failed",
				       errcode);
			return errcode;
		}
		packet.type = ppt_tma;
	} else if (strcmp(directive, "mtc") == 0) {
		errcode = parse_uint8(&packet.payload.mtc.ctc, payload);
		if (errcode < 0) {
			yasm_print_err(p->y, "mtc: parsing mtc failed",
				       errcode);
			return errcode;
		}
		packet.type = ppt_mtc;
	} else if (strcmp(directive, "cyc") == 0) {
		errcode = parse_uint64(&packet.payload.cyc.value, payload);
		if (errcode < 0) {
			yasm_print_err(p->y, "cyc: parsing cyc failed",
				       errcode);
			return errcode;
		}
		packet.type = ppt_cyc;
	} else if (strcmp(directive, "vmcs") == 0) {
		errcode = parse_uint64(&packet.payload.vmcs.base, payload);
		if (errcode < 0) {
			yasm_print_err(p->y, "vmcs: parsing failed", errcode);
			return errcode;
		}
		packet.type = ppt_vmcs;
	} else if (strcmp(directive, "mnt") == 0) {
		errcode = parse_uint64(&packet.payload.mnt.payload, payload);
		if (errcode < 0) {
			yasm_print_err(p->y, "mnt: parsing failed", errcode);
			return errcode;
		}
		packet.type = ppt_mnt;
	} else if (strcmp(directive, "exstop") == 0) {
		packet.type = ppt_exstop;
		memset(&packet.payload.exstop, 0,
		       sizeof(packet.payload.exstop));

		if (strcmp(payload, "ip") == 0)
			packet.payload.exstop.ip = 1;
		else if (*payload) {
			yasm_print_err(p->y, "exstop: parsing failed",
				       -err_parse_trailing_tokens);
			return -err_parse_trailing_tokens;
		}
	} else if (strcmp(directive, "mwait") == 0) {
		errcode = parse_mwait(&packet.payload.mwait.hints,
				      &packet.payload.mwait.ext, payload);
		if (errcode < 0) {
			yasm_print_err(p->y, "mwait: parsing failed", errcode);
			return errcode;
		}

		packet.type = ppt_mwait;
	} else if (strcmp(directive, "pwre") == 0) {
		char *token;

		packet.type = ppt_pwre;
		memset(&packet.payload.pwre, 0, sizeof(packet.payload.pwre));

		token = strtok(payload, " , ");
		errcode = parse_c_state(&packet.payload.pwre.state,
					&packet.payload.pwre.sub_state, token);
		if (errcode < 0) {
			yasm_print_err(p->y, "pwre: bad C-state", errcode);
			return errcode;
		}

		token = strtok(NULL, " ,");
		if (token) {
			if (strcmp(token, "hw") == 0)
				packet.payload.pwre.hw = 1;
			else {
				yasm_print_err(p->y, "pwre: parsing failed",
					       -err_parse_trailing_tokens);
				return -err_parse_trailing_tokens;
			}
		}
	} else if (strcmp(directive, "pwrx") == 0) {
		char *token;

		packet.type = ppt_pwrx;
		memset(&packet.payload.pwrx, 0, sizeof(packet.payload.pwrx));

		token = strtok(payload, ":");
		if (!token) {
			yasm_print_err(p->y, "pwrx: parsing failed",
				       -err_parse_no_args);
			return -err_parse_no_args;
		}

		if (strcmp(token, "int") == 0)
			packet.payload.pwrx.interrupt = 1;
		else if (strcmp(token, "st") == 0)
			packet.payload.pwrx.store = 1;
		else if (strcmp(token, "hw") == 0)
			packet.payload.pwrx.autonomous = 1;
		else {
			yasm_print_err(p->y, "pwrx: bad wake reason",
				       -err_parse);
			return -err_parse;
		}

		token = strtok(NULL, " ,");
		errcode = parse_c_state(&packet.payload.pwrx.last, NULL, token);
		if (errcode < 0) {
			yasm_print_err(p->y, "pwrx: bad last C-state", errcode);
			return errcode;
		}

		token = strtok(NULL, " ,");
		errcode = parse_c_state(&packet.payload.pwrx.deepest, NULL,
					token);
		if (errcode < 0) {
			yasm_print_err(p->y, "pwrx: bad deepest C-state",
				       errcode);
			return errcode;
		}
	} else if (strcmp(directive, "ptw") == 0) {
		char *token;

		packet.type = ppt_ptw;
		memset(&packet.payload.ptw, 0, sizeof(packet.payload.ptw));

		token = strtok(payload, ":");
		if (!token) {
			yasm_print_err(p->y, "ptw: parsing failed",
				       -err_parse_no_args);
			return -err_parse_no_args;
		}

		errcode = str_to_uint8(token, &packet.payload.ptw.plc, 0);
		if (errcode < 0) {
			yasm_print_err(p->y, "ptw: bad payload size", errcode);
			return errcode;
		}

		token = strtok(NULL, ", ");
		if (!token) {
			yasm_print_err(p->y, "ptw: no payload",
				       -err_parse_no_args);
			return -err_parse_no_args;
		}

		errcode = str_to_uint64(token, &packet.payload.ptw.payload, 0);
		if (errcode < 0) {
			yasm_print_err(p->y, "ptw: bad payload", errcode);
			return errcode;
		}

		token = strtok(NULL, " ");
		if (token) {
			if (strcmp(token, "ip") != 0) {
				yasm_print_err(p->y, "ptw: parse error",
					       -err_parse_trailing_tokens);
				return -err_parse_trailing_tokens;
			}

			packet.payload.ptw.ip = 1;
		}
	} else if (strcmp(directive, "cfe") == 0) {
		const char *cpl;

		cpl = (const char *) payload;
		errcode = parse_cfe(&cpl, p, &packet.payload.cfe);
		if (errcode != 0) {
			if (errcode < 0)
				yasm_print_err(p->y, "cfe: parsing failed",
					       errcode);
			else
				errcode = -err_parse;

			return errcode;
		}

		packet.type = ppt_cfe;
	} else if (strcmp(directive, "evd") == 0) {
		const char *cpl;

		cpl = (const char *) payload;
		errcode = parse_evd(&cpl, p, &packet.payload.evd);
		if (errcode != 0) {
			if (errcode < 0)
				yasm_print_err(p->y, "evd: parsing failed",
					       errcode);
			else
				errcode = -err_parse;

			return errcode;
		}

		packet.type = ppt_evd;
	} else if (strcmp(directive, "raw-8") == 0) {
		uint8_t value;

		errcode = parse_uint8(&value, payload);
		if (errcode < 0) {
			yasm_print_err(p->y, payload, errcode);
			return errcode;
		}

		return pt_raw_8(p, e, value);
	} else if (strcmp(directive, "raw-16") == 0) {
		uint16_t value;

		errcode = parse_uint16(&value, payload);
		if (errcode < 0) {
			yasm_print_err(p->y, payload, errcode);
			return errcode;
		}

		return pt_raw_16(p, e, value);
	} else if (strcmp(directive, "raw-32") == 0) {
		uint32_t value;

		errcode = parse_uint32(&value, payload);
		if (errcode < 0) {
			yasm_print_err(p->y, payload, errcode);
			return errcode;
		}

		return pt_raw_32(p, e, value);
	} else if (strcmp(directive, "raw-64") == 0) {
		uint64_t value;

		errcode = parse_uint64(&value, payload);
		if (errcode < 0) {
			yasm_print_err(p->y, payload, errcode);
			return errcode;
		}

		return pt_raw_64(p, e, value);
	} else if (strcmp(directive, "trig") == 0) {
		const char *cpl;

		cpl = (const char *) payload;
		errcode = parse_trig(&cpl, p, &packet.payload.trig);
		if (errcode != 0) {
			if (errcode < 0)
				yasm_print_err(p->y, "trig: parsing failed",
					       errcode);
			else
				errcode = -err_parse;

			return errcode;
		}

		packet.type = ppt_trig;
	} else {
		errcode = yasm_print_err(p->y, "invalid syntax",
					 -err_parse_unknown_directive);
		return errcode;
	}

	bytes_written = pt_enc_next(e, &packet);
	if (bytes_written < 0) {
		char msg[128];

		snprintf(msg, sizeof(msg),
			 "encoder error in directive %s (status %s)", directive,
			 pt_errstr(pt_errcode(bytes_written)));

		yasm_print_err(p->y, msg, -err_pt_lib);
	} else
		p->pt_bytes_written += bytes_written;

	return bytes_written;
}

#if defined(FEATURE_SIDEBAND)

static int sb_open(struct parser *p, const char *fmt, const char *src,
		   const char *prio)
{
	struct sb_filelist *sbfiles;
	const char *root;
	char name[FILENAME_MAX];
	FILE *file;
	int errcode;

	if (bug_on(!p) || bug_on(!p->y) || bug_on(!prio))
		return -err_internal;

	root = p->y->fileroot;
	if (!root) {
		yasm_print_err(p->y, "open - name root", -err_internal);
		return -err_internal;
	}

	if (src && *src)
		snprintf(name, sizeof(name), "%s-%s-%s-%s%s", root, src, fmt,
			 prio, sb_suffix);
	else
		snprintf(name, sizeof(name), "%s-%s-%s%s", root, fmt, prio,
			 sb_suffix);

	for (sbfiles = p->sbfiles; sbfiles; sbfiles = sbfiles->next) {
		if (strncmp(sbfiles->sbfile.name, name, sizeof(name)) == 0)
			break;
	}

	if (!sbfiles) {
		file = fopen(name, "w");
		if (!file) {
			yasm_print_err(p->y, name, -err_file_open);
			return -err_file_open;
		}

		sbfiles = malloc(sizeof(*sbfiles));
		if (!sbfiles) {
			yasm_print_err(p->y, "open", -err_no_mem);
			fclose(file);
			return -err_no_mem;
		}

		memset(&sbfiles->sbfile, 0, sizeof(sbfiles->sbfile));

		errcode = duplicate_name(&sbfiles->sbfile.name, name,
					 FILENAME_MAX);
		if (errcode < 0) {
			yasm_print_err(p->y, "open", errcode);
			fclose(file);
			free(sbfiles);
			return errcode;
		}

		sbfiles->sbfile.file = file;
		sbfiles->sbfile.format = sbf_raw;

		sbfiles->next = p->sbfiles;
		p->sbfiles = sbfiles;
	}

	p->current_sbfile = &sbfiles->sbfile;
	return 0;
}

static struct sb_file *p_get_current_sbfile(struct parser *p)
{
	struct sb_file *sb;

	if (bug_on(!p))
		return NULL;

	sb = p->current_sbfile;
	if (!sb) {
		yasm_print_err(p->y, "no sideband file", -err_sb_missing);
		return NULL;
	}

	if (bug_on(!sb->file)) {
		yasm_print_err(p->y, "corrupt sideband file", -err_internal);
		return NULL;
	}

	return sb;
}

static int sb_set_format(struct parser *p, struct sb_file *sb,
			 enum sb_format format)
{
	if (bug_on(!p))
		return -err_internal;

	if (!sb)
		return -err_sb_missing;

	switch (format) {
	case sbf_raw:
		/* Raw sideband directives are allowed for all formats. */
		return 0;

#if defined(FEATURE_PEVENT)
	case sbf_pevent:
		switch (sb->format) {
		case sbf_pevent:
			return 0;

		case sbf_raw:
			sb->format = sbf_pevent;

			memset(&sb->variant.pevent, 0,
			       sizeof(sb->variant.pevent));
			sb->variant.pevent.config.size =
				sizeof(sb->variant.pevent.config);
			sb->variant.pevent.config.time_shift = 0;
			sb->variant.pevent.config.time_mult = 1;
			sb->variant.pevent.config.time_zero = 0ull;
			return 0;

		default:
			yasm_print_err(p->y, "mixing sideband formats",
				       -err_sb_mix);
			return -err_sb_mix;
		}
#endif /* defined(FEATURE_PEVENT) */
	}

	yasm_print_err(p->y, "unknown sideband format", -err_internal);
	return -err_internal;
}

static int sb_raw(struct parser *p, const void *buffer, size_t size)
{
	struct sb_file *sb;
	size_t written;
	int errcode;

	if (bug_on(!p))
		return -err_internal;

	sb = p_get_current_sbfile(p);
	if (!sb)
		return -err_sb_missing;

	errcode = sb_set_format(p, sb, sbf_raw);
	if (errcode < 0)
		return errcode;

	written = fwrite(buffer, size, 1, sb->file);
	if (written != 1) {
		yasm_print_err(p->y, "write failed", -err_file_write);
		return -err_file_write;
	}

	sb->bytes_written += (int) size;
	return 0;
}

static int sb_raw_8(struct parser *p, uint8_t value)
{
	return sb_raw(p, &value, sizeof(value));
}

static int sb_raw_16(struct parser *p, uint16_t value)
{
	return sb_raw(p, &value, sizeof(value));
}

static int sb_raw_32(struct parser *p, uint32_t value)
{
	return sb_raw(p, &value, sizeof(value));
}

static int sb_raw_64(struct parser *p, uint64_t value)
{
	return sb_raw(p, &value, sizeof(value));
}

#if defined(FEATURE_PEVENT)

/* A buffer to hold sample values to which a pev_event can point. */

struct pev_sample_buffer {
	uint32_t pid;
	uint32_t tid;
	uint64_t time;
	uint64_t id;
	uint64_t stream_id;
	uint32_t cpu;
	uint64_t identifier;
};

static int pevent_sample_type(struct parser *p, uint64_t sample_type)
{
	struct sb_file *sb;
	int errcode;

	sb = p_get_current_sbfile(p);
	if (!sb)
		return -err_sb_missing;

	errcode = sb_set_format(p, sb, sbf_pevent);
	if (errcode < 0)
		return errcode;

	if (sb->variant.pevent.is_final) {
		yasm_print_err(p->y,
			       "the sideband configuration can no longer be "
			       "modified", -err_sb_final);
		return -err_sb_final;
	}

	sb->variant.pevent.config.sample_type = sample_type;
	return 0;
}

static int pevent_process_samples(struct pev_event *event,
				  struct pev_sample_buffer *samples,
				  struct parser *p,
				  const struct pev_config *config,
				  char *payload)
{
	char *token;

	if (bug_on(!event) || bug_on(!samples) || bug_on(!config))
		return -err_internal;

	if (config->sample_type & PERF_SAMPLE_TID) {
		int errcode;

		token = strtok(payload, " ,");
		if (!token) {
			yasm_print_err(p->y, "pid missing", -err_parse);
			return -err_parse;
		}

		payload = NULL;

		errcode = str_to_uint32(token, &samples->pid, 0);
		if (errcode < 0) {
			yasm_print_err(p->y, "bad pid", errcode);
			return errcode;
		}

		token = strtok(payload, " ,");
		if (!token) {
			yasm_print_err(p->y, "tid missing", -err_parse);
			return -err_parse;
		}

		errcode = str_to_uint32(token, &samples->tid, 0);
		if (errcode < 0) {
			yasm_print_err(p->y, "bad tid", errcode);
			return errcode;
		}

		event->sample.pid = &samples->pid;
		event->sample.tid = &samples->tid;
	}

	if (config->sample_type & PERF_SAMPLE_TIME) {
		int errcode;

		token = strtok(payload, " ,");
		if (!token) {
			yasm_print_err(p->y, "tsc missing", -err_parse);
			return -err_parse;
		}

		payload = NULL;

		errcode = str_to_uint64(token, &event->sample.tsc, 0);
		if (errcode < 0) {
			yasm_print_err(p->y, "bad tsc", errcode);
			return errcode;
		}

		errcode = pev_time_from_tsc(&samples->time, event->sample.tsc,
					    config);
		if (errcode < 0) {
			fprintf(stderr, "error converting tsc %"PRIx64": %s\n",
				event->sample.tsc,
				pt_errstr(pt_errcode(errcode)));
			return -err_pt_lib;
		}

		event->sample.time = &samples->time;
	}

	if (config->sample_type & PERF_SAMPLE_ID) {
		int errcode;

		token = strtok(payload, " ,");
		if (!token) {
			yasm_print_err(p->y, "id missing", -err_parse);
			return -err_parse;
		}

		payload = NULL;

		errcode = str_to_uint64(token, &samples->id, 0);
		if (errcode < 0) {
			yasm_print_err(p->y, "bad id", errcode);
			return errcode;
		}

		event->sample.id = &samples->id;
	}

	if (config->sample_type & PERF_SAMPLE_CPU) {
		int errcode;

		token = strtok(payload, " ,");
		if (!token) {
			yasm_print_err(p->y, "cpu missing", -err_parse);
			return -err_parse;
		}

		payload = NULL;

		errcode = str_to_uint32(token, &samples->cpu, 0);
		if (errcode < 0) {
			yasm_print_err(p->y, "bad cpu", errcode);
			return errcode;
		}

		event->sample.cpu = &samples->cpu;
	}

	if (config->sample_type & PERF_SAMPLE_STREAM_ID) {
		int errcode;

		token = strtok(payload, " ,");
		if (!token) {
			yasm_print_err(p->y, "stream missing", -err_parse);
			return -err_parse;
		}

		payload = NULL;

		errcode = str_to_uint64(token, &samples->stream_id, 0);
		if (errcode < 0) {
			yasm_print_err(p->y, "bad stream", errcode);
			return errcode;
		}

		event->sample.stream_id = &samples->stream_id;
	}

	if (config->sample_type & PERF_SAMPLE_IDENTIFIER) {
		int errcode;

		token = strtok(payload, " ,");
		if (!token) {
			yasm_print_err(p->y, "identifier missing", -err_parse);
			return -err_parse;
		}

		payload = NULL;

		errcode = str_to_uint64(token, &samples->identifier, 0);
		if (errcode < 0) {
			yasm_print_err(p->y, "bad identifier", errcode);
			return errcode;
		}

		event->sample.identifier = &samples->identifier;
	}

	token = strtok(payload, " ,");
	if (token) {
		yasm_print_err(p->y, "unexpected samples", -err_parse);
		return -err_parse;
	}

	return 0;
}

static int sb_pevent(struct parser *p, struct pev_event *event, char *payload)
{
	const struct pev_config *config;
	struct pev_sample_buffer samples;
	struct sb_file *sb;
	uint8_t raw[FILENAME_MAX];
	int errcode, size;

	memset(raw, 0, sizeof(raw));

	sb = p_get_current_sbfile(p);
	if (!sb)
		return -err_sb_missing;

	errcode = sb_set_format(p, sb, sbf_pevent);
	if (errcode < 0)
		return errcode;

	config = &sb->variant.pevent.config;

	errcode = pevent_process_samples(event, &samples, p, config, payload);
	if (errcode < 0)
		return errcode;

	size = pev_write(event, raw, raw + sizeof(raw), config);
	if (size < 0) {
		fprintf(stderr, "error writing pevent sample: %s\n",
			pt_errstr(pt_errcode(size)));
		return -err_pt_lib;
	}

	/* Emitting a pevent sideband event finalizes the configuration. */
	sb->variant.pevent.is_final = 1;

	return sb_raw(p, raw, (size_t) size);
}

static int pevent_mmap_section(struct parser *p, const char *section,
			       const char *pid, const char *tid)
{
	union {
		struct pev_record_mmap mmap;
		uint8_t buffer[FILENAME_MAX];
	} record;
	struct pev_event event;
	const char *filename;
	uint64_t start, org;
	int errcode;

	if (bug_on(!p) || bug_on(!p->y))
		return -err_internal;

	memset(record.buffer, 0, sizeof(record.buffer));
	memset(&event, 0, sizeof(event));

	filename = p->y->binfile;
	if (!filename) {
		yasm_print_err(p->y, "pevent-mmap-section - filename",
			       -err_internal);
		return -err_internal;
	}

	strncpy(record.mmap.filename, filename,
		sizeof(record.buffer) - sizeof(record.mmap) - 1);

	errcode = str_to_uint32(pid, &record.mmap.pid, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-mmap-section - pid", errcode);
		return errcode;
	}

	errcode = str_to_uint32(tid, &record.mmap.tid, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-mmap-section - tid", errcode);
		return errcode;
	}

	errcode = yasm_lookup_section_label(p->y, section, "vstart",
					    &record.mmap.addr);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-mmap-section - section vstart",
			       errcode);
		return errcode;
	}

	errcode = yasm_lookup_section_label(p->y, section, "length",
					    &record.mmap.len);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-mmap-section - section length",
			       errcode);
		return errcode;
	}

	errcode = yasm_lookup_section_label(p->y, section, "start",
					    &start);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-mmap-section - section start",
			       errcode);
		return errcode;
	}

	errcode = yasm_lookup_label(p->y, &org, "org");
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-mmap-section - org",
			       errcode);
		return errcode;
	}

	if (start < org) {
		yasm_print_err(p->y, "corrupt section labels", -err_internal);
		return -err_internal;
	}

	record.mmap.pgoff = start - org;

	event.type = PERF_RECORD_MMAP;
	event.record.mmap = &record.mmap;

	return sb_pevent(p, &event, NULL);
}

static int pevent_mmap(struct parser *p, const char *pid, const char *tid,
		       const char *addr, const char *len, const char *pgoff,
		       const char *filename)
{
	union {
		struct pev_record_mmap mmap;
		uint8_t buffer[FILENAME_MAX];
	} record;
	struct pev_event event;
	int errcode;

	if (bug_on(!p) || bug_on(!p->y))
		return -err_internal;

	memset(record.buffer, 0, sizeof(record.buffer));
	memset(&event, 0, sizeof(event));

	errcode = str_to_uint32(pid, &record.mmap.pid, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-mmap-section - pid", errcode);
		return errcode;
	}

	errcode = str_to_uint32(tid, &record.mmap.tid, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-mmap-section - tid", errcode);
		return errcode;
	}

	errcode = str_to_uint64(addr, &record.mmap.addr, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-mmap-section - addr", errcode);
		return errcode;
	}

	errcode = str_to_uint64(len, &record.mmap.len, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-mmap-section - len", errcode);
		return errcode;
	}

	errcode = str_to_uint64(pgoff, &record.mmap.pgoff, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-mmap-section - pgoff", errcode);
		return errcode;
	}

	strncpy(record.mmap.filename, filename,
		sizeof(record.buffer) - sizeof(record.mmap) - 1);

	event.type = PERF_RECORD_MMAP;
	event.record.mmap = &record.mmap;

	return sb_pevent(p, &event, NULL);
}

static int pevent_lost(struct parser *p, const char *id, const char *lost)
{
	union {
		struct pev_record_lost lost;
		uint8_t buffer[1024];
	} record;
	struct pev_event event;
	int errcode;

	if (bug_on(!p) || bug_on(!p->y))
		return -err_internal;

	memset(record.buffer, 0, sizeof(record.buffer));
	memset(&event, 0, sizeof(event));

	errcode = str_to_uint64(id, &record.lost.id, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-lost - id", errcode);
		return errcode;
	}

	errcode = str_to_uint64(lost, &record.lost.lost, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-lost - lost", errcode);
		return errcode;
	}

	event.type = PERF_RECORD_LOST;
	event.record.lost = &record.lost;

	return sb_pevent(p, &event, NULL);
}

static int pevent_comm(struct parser *p, const char *pid, const char *tid,
		       const char *comm, uint16_t misc)
{
	union {
		struct pev_record_comm comm;
		uint8_t buffer[FILENAME_MAX];
	} record;
	struct pev_event event;
	size_t limit;
	int errcode, len;

	if (bug_on(!p) || bug_on(!p->y))
		return -err_internal;

	memset(record.buffer, 0, sizeof(record.buffer));
	memset(&event, 0, sizeof(event));

	errcode = str_to_uint32(pid, &record.comm.pid, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-comm - pid", errcode);
		return errcode;
	}

	errcode = str_to_uint32(tid, &record.comm.tid, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-comm - tid", errcode);
		return errcode;
	}

	limit = sizeof(record.buffer) - sizeof(record.comm);
	len = snprintf(record.comm.comm, limit, "%s", comm);
	if (len < 0)
		return -err_parse;
	if (limit <= (size_t) len)
		return -err_name_too_long;

	event.type = PERF_RECORD_COMM;
	event.misc = misc;
	event.record.comm = &record.comm;

	return sb_pevent(p, &event, NULL);
}

static int pevent_exit(struct parser *p, const char *pid, const char *ppid,
		       const char *tid, const char *ptid, const char *time)
{
	union {
		struct pev_record_exit exit;
		uint8_t buffer[1024];
	} record;
	struct pev_event event;
	int errcode;

	if (bug_on(!p) || bug_on(!p->y))
		return -err_internal;

	memset(record.buffer, 0, sizeof(record.buffer));
	memset(&event, 0, sizeof(event));

	errcode = str_to_uint32(pid, &record.exit.pid, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-exit - pid", errcode);
		return errcode;
	}

	errcode = str_to_uint32(ppid, &record.exit.ppid, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-exit - ppid", errcode);
		return errcode;
	}

	errcode = str_to_uint32(tid, &record.exit.tid, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-exit - tid", errcode);
		return errcode;
	}

	errcode = str_to_uint32(ptid, &record.exit.ptid, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-exit - ptid", errcode);
		return errcode;
	}


	errcode = str_to_uint64(time, &record.exit.time, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-exit - time", errcode);
		return errcode;
	}

	event.type = PERF_RECORD_EXIT;
	event.record.exit = &record.exit;

	return sb_pevent(p, &event, NULL);
}

static int pevent_fork(struct parser *p, const char *pid, const char *ppid,
		       const char *tid, const char *ptid, const char *time)
{
	union {
		struct pev_record_fork fork;
		uint8_t buffer[1024];
	} record;
	struct pev_event event;
	int errcode;

	if (bug_on(!p) || bug_on(!p->y))
		return -err_internal;

	memset(record.buffer, 0, sizeof(record.buffer));
	memset(&event, 0, sizeof(event));

	errcode = str_to_uint32(pid, &record.fork.pid, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-fork - pid", errcode);
		return errcode;
	}

	errcode = str_to_uint32(ppid, &record.fork.ppid, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-fork - ppid", errcode);
		return errcode;
	}

	errcode = str_to_uint32(tid, &record.fork.tid, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-fork - tid", errcode);
		return errcode;
	}

	errcode = str_to_uint32(ptid, &record.fork.ptid, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-fork - ptid", errcode);
		return errcode;
	}


	errcode = str_to_uint64(time, &record.fork.time, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-fork - time", errcode);
		return errcode;
	}

	event.type = PERF_RECORD_FORK;
	event.record.fork = &record.fork;

	return sb_pevent(p, &event, NULL);
}

static int pevent_aux(struct parser *p, const char *offset, const char *size,
		      const char *flags)
{
	union {
		struct pev_record_aux aux;
		uint8_t buffer[1024];
	} record;
	struct pev_event event;
	int errcode;

	if (bug_on(!p) || bug_on(!p->y))
		return -err_internal;

	memset(record.buffer, 0, sizeof(record.buffer));
	memset(&event, 0, sizeof(event));

	errcode = str_to_uint64(offset, &record.aux.aux_offset, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-aux - offset", errcode);
		return errcode;
	}

	errcode = str_to_uint64(size, &record.aux.aux_size, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-aux - size", errcode);
		return errcode;
	}

	errcode = str_to_uint64(flags, &record.aux.flags, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-aux - flags", errcode);
		return errcode;
	}

	event.type = PERF_RECORD_AUX;
	event.record.aux = &record.aux;

	return sb_pevent(p, &event, NULL);
}

static int pevent_itrace_start(struct parser *p, const char *pid,
			       const char *tid)
{
	union {
		struct pev_record_itrace_start itrace_start;
		uint8_t buffer[1024];
	} record;
	struct pev_event event;
	int errcode;

	if (bug_on(!p) || bug_on(!p->y))
		return -err_internal;

	memset(record.buffer, 0, sizeof(record.buffer));
	memset(&event, 0, sizeof(event));

	errcode = str_to_uint32(pid, &record.itrace_start.pid, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-itrace-start - pid", errcode);
		return errcode;
	}

	errcode = str_to_uint32(tid, &record.itrace_start.tid, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-itrace-start - tid", errcode);
		return errcode;
	}

	event.type = PERF_RECORD_ITRACE_START;
	event.record.itrace_start = &record.itrace_start;

	return sb_pevent(p, &event, NULL);
}

static int pevent_lost_samples(struct parser *p, const char *lost)
{
	union {
		struct pev_record_lost_samples lost_samples;
		uint8_t buffer[1024];
	} record;
	struct pev_event event;
	int errcode;

	if (bug_on(!p) || bug_on(!p->y))
		return -err_internal;

	memset(record.buffer, 0, sizeof(record.buffer));
	memset(&event, 0, sizeof(event));

	errcode = str_to_uint64(lost, &record.lost_samples.lost, 0);
	if (errcode < 0) {
		yasm_print_err(p->y, "pevent-lost-samples - lost", errcode);
		return errcode;
	}

	event.type = PERF_RECORD_LOST_SAMPLES;
	event.record.lost_samples = &record.lost_samples;

	return sb_pevent(p, &event, NULL);
}

static int pevent_switch(struct parser *p, uint16_t misc, char *payload)
{
	struct pev_event event;

	if (bug_on(!p) || bug_on(!p->y))
		return -err_internal;

	memset(&event, 0, sizeof(event));

	event.type = PERF_RECORD_SWITCH;
	event.misc = misc;

	return sb_pevent(p, &event, payload);
}

static int pevent_switch_cpu_wide(struct parser *p, const char *pid,
				  const char *tid, uint16_t misc)
{
	union {
		struct pev_record_switch_cpu_wide switch_cpu_wide;
		uint8_t buffer[1024];
	} record;
	struct pev_event event;
	int errcode;

	if (bug_on(!p) || bug_on(!p->y))
		return -err_internal;

	memset(record.buffer, 0, sizeof(record.buffer));
	memset(&event, 0, sizeof(event));

	event.misc = misc;

	errcode = str_to_uint32(pid, &record.switch_cpu_wide.next_prev_pid, 0);
	if (errcode < 0) {
		yasm_print_err(p->y,
			       "pevent-switch-cpu-wide - next_prev_pid",
			       errcode);
		return errcode;
	}

	errcode = str_to_uint32(tid, &record.switch_cpu_wide.next_prev_tid, 0);
	if (errcode < 0) {
		yasm_print_err(p->y,
			       "pevent-switch-cpu-wide - next_prev_tid",
			       errcode);
		return errcode;
	}

	event.type = PERF_RECORD_SWITCH_CPU_WIDE;
	event.record.switch_cpu_wide = &record.switch_cpu_wide;

	return sb_pevent(p, &event, NULL);
}
#endif /* defined(FEATURE_PEVENT) */

/* Process a @sb directive.
 *
 * Returns 0 on success; a negative enum errcode otherwise.
 * Returns -err_internal if @p is the NULL pointer.
 * Returns -err_parse if a general parsing error was encountered.
 * Returns -err_parse_unknown_directive if there was an unknown pt directive.
 */
static int p_process_sb(struct parser *p)
{
	struct pt_directive *pd;
	char *directive, *payload;

	if (bug_on(!p))
		return -err_internal;

	pd = p->pd;
	if (!pd)
		return -err_internal;

	directive = pd->name;
	payload = pd->payload;

	if (strcmp(directive, "primary") == 0) {
		char *fmt, *src;

		fmt = strtok(payload, " ,");
		if (!fmt) {
			yasm_print_err(p->y, "primary - format missing",
				       -err_parse_no_args);
			return -err_parse_no_args;
		}

		src = strtok(NULL, " ");

		return sb_open(p, fmt, src, "primary");
	} else if (strcmp(directive, "secondary") == 0) {
		char *fmt, *src;

		fmt = strtok(payload, " ,");
		if (!fmt) {
			yasm_print_err(p->y, "secondary - format missing",
				       -err_parse_no_args);
			return -err_parse_no_args;
		}

		src = strtok(NULL, " ");

		return sb_open(p, fmt, src, "secondary");
	} else if (strcmp(directive, "raw-8") == 0) {
		uint8_t value;
		int errcode;

		errcode = parse_uint8(&value, payload);
		if (errcode < 0) {
			yasm_print_err(p->y, payload, errcode);
			return errcode;
		}

		return sb_raw_8(p, value);
	} else if (strcmp(directive, "raw-16") == 0) {
		uint16_t value;
		int errcode;

		errcode = parse_uint16(&value, payload);
		if (errcode < 0) {
			yasm_print_err(p->y, payload, errcode);
			return errcode;
		}

		return sb_raw_16(p, value);
	} else if (strcmp(directive, "raw-32") == 0) {
		uint32_t value;
		int errcode;

		errcode = parse_uint32(&value, payload);
		if (errcode < 0) {
			yasm_print_err(p->y, payload, errcode);
			return errcode;
		}

		return sb_raw_32(p, value);
	} else if (strcmp(directive, "raw-64") == 0) {
		uint64_t value;
		int errcode;

		errcode = parse_uint64(&value, payload);
		if (errcode < 0) {
			yasm_print_err(p->y, payload, errcode);
			return errcode;
		}

		return sb_raw_64(p, value);
#if defined(FEATURE_PEVENT)
	} else if (strcmp(directive, "pevent-sample_type") == 0) {
		uint64_t sample_type;
		char *token;

		sample_type = 0ull;

		for (token = strtok(payload, " ,"); token;
		     token = strtok(NULL, " ,")) {

			if (strcmp(token, "tid") == 0)
				sample_type |= (uint64_t) PERF_SAMPLE_TID;
			else if (strcmp(token, "time") == 0)
				sample_type |= (uint64_t) PERF_SAMPLE_TIME;
			else if (strcmp(token, "id") == 0)
				sample_type |= (uint64_t) PERF_SAMPLE_ID;
			else if (strcmp(token, "stream") == 0)
				sample_type |= (uint64_t) PERF_SAMPLE_STREAM_ID;
			else if (strcmp(token, "cpu") == 0)
				sample_type |= (uint64_t) PERF_SAMPLE_CPU;
			else if (strcmp(token, "identifier") == 0)
				sample_type |=
					(uint64_t) PERF_SAMPLE_IDENTIFIER;
			else {
				uint64_t value;
				int errcode;

				errcode = parse_uint64(&value, payload);
				if (errcode < 0) {
					yasm_print_err(p->y, token, errcode);
					return errcode;
				}

				sample_type |= value;
			}
		}

		return pevent_sample_type(p, sample_type);

	} else if (strcmp(directive, "pevent-mmap-section") == 0) {
		char *section, *pid, *tid;

		section = strtok(payload, " ,");
		if (!section) {
			yasm_print_err(p->y, "section missing", -err_parse);
			return -err_parse;
		}

		pid = strtok(NULL, " ,");
		if (!pid) {
			yasm_print_err(p->y, "pid missing", -err_parse);
			return -err_parse;
		}

		tid = strtok(NULL, " ,");
		if (!tid) {
			yasm_print_err(p->y, "tid missing", -err_parse);
			return -err_parse;
		}

		return pevent_mmap_section(p, section, pid, tid);
	} else if (strcmp(directive, "pevent-mmap") == 0) {
		char *pid, *tid, *addr, *len, *pgoff, *filename;

		pid = strtok(payload, " ,");
		if (!pid) {
			yasm_print_err(p->y, "pid missing", -err_parse);
			return -err_parse;
		}

		tid = strtok(NULL, " ,");
		if (!tid) {
			yasm_print_err(p->y, "tid missing", -err_parse);
			return -err_parse;
		}

		addr = strtok(NULL, " ,");
		if (!addr) {
			yasm_print_err(p->y, "addr missing", -err_parse);
			return -err_parse;
		}

		len = strtok(NULL, " ,");
		if (!len) {
			yasm_print_err(p->y, "len missing", -err_parse);
			return -err_parse;
		}

		pgoff = strtok(NULL, " ,");
		if (!pgoff) {
			yasm_print_err(p->y, "pgoff missing", -err_parse);
			return -err_parse;
		}

		filename = strtok(NULL, " ,");
		if (!filename) {
			yasm_print_err(p->y, "filename missing", -err_parse);
			return -err_parse;
		}

		return pevent_mmap(p, pid, tid, addr, len, pgoff, filename);
	} else if (strcmp(directive, "pevent-lost") == 0) {
		char *id, *lost;

		id = strtok(payload, " ,");
		if (!id) {
			yasm_print_err(p->y, "id missing", -err_parse);
			return -err_parse;
		}

		lost = strtok(NULL, " ,");
		if (!lost) {
			yasm_print_err(p->y, "lost missing", -err_parse);
			return -err_parse;
		}

		return pevent_lost(p, id, lost);
	} else if (strcmp(directive, "pevent-comm") == 0) {
		char *pid, *tid, *comm;

		pid = strtok(payload, " ,");
		if (!pid) {
			yasm_print_err(p->y, "pid missing", -err_parse);
			return -err_parse;
		}

		tid = strtok(NULL, " ,");
		if (!tid) {
			yasm_print_err(p->y, "tid missing", -err_parse);
			return -err_parse;
		}

		comm = strtok(NULL, " ,");
		if (!comm) {
			yasm_print_err(p->y, "comm missing", -err_parse);
			return -err_parse;
		}

		return pevent_comm(p, pid, tid, comm, 0);
	} else if (strcmp(directive, "pevent-comm.exec") == 0) {
		char *pid, *tid, *comm;

		pid = strtok(payload, " ,");
		if (!pid) {
			yasm_print_err(p->y, "pid missing", -err_parse);
			return -err_parse;
		}

		tid = strtok(NULL, " ,");
		if (!tid) {
			yasm_print_err(p->y, "tid missing", -err_parse);
			return -err_parse;
		}

		comm = strtok(NULL, " ,");
		if (!comm) {
			yasm_print_err(p->y, "comm missing", -err_parse);
			return -err_parse;
		}

		return pevent_comm(p, pid, tid, comm,
				   PERF_RECORD_MISC_COMM_EXEC);
	} else if (strcmp(directive, "pevent-exit") == 0) {
		char *pid, *ppid, *tid, *ptid, *time;

		pid = strtok(payload, " ,");
		if (!pid) {
			yasm_print_err(p->y, "pid missing", -err_parse);
			return -err_parse;
		}

		ppid = strtok(NULL, " ,");
		if (!ppid) {
			yasm_print_err(p->y, "ppid missing", -err_parse);
			return -err_parse;
		}

		tid = strtok(NULL, " ,");
		if (!tid) {
			yasm_print_err(p->y, "tid missing", -err_parse);
			return -err_parse;
		}

		ptid = strtok(NULL, " ,");
		if (!ptid) {
			yasm_print_err(p->y, "ptid missing", -err_parse);
			return -err_parse;
		}

		time = strtok(NULL, " ,");
		if (!time) {
			yasm_print_err(p->y, "time missing", -err_parse);
			return -err_parse;
		}

		return pevent_exit(p, pid, ppid, tid, ptid, time);
	} else if (strcmp(directive, "pevent-fork") == 0) {
		char *pid, *ppid, *tid, *ptid, *time;

		pid = strtok(payload, " ,");
		if (!pid) {
			yasm_print_err(p->y, "pid missing", -err_parse);
			return -err_parse;
		}

		ppid = strtok(NULL, " ,");
		if (!ppid) {
			yasm_print_err(p->y, "ppid missing", -err_parse);
			return -err_parse;
		}

		tid = strtok(NULL, " ,");
		if (!tid) {
			yasm_print_err(p->y, "tid missing", -err_parse);
			return -err_parse;
		}

		ptid = strtok(NULL, " ,");
		if (!ptid) {
			yasm_print_err(p->y, "ptid missing", -err_parse);
			return -err_parse;
		}

		time = strtok(NULL, " ,");
		if (!time) {
			yasm_print_err(p->y, "time missing", -err_parse);
			return -err_parse;
		}

		return pevent_fork(p, pid, ppid, tid, ptid, time);
	} else if (strcmp(directive, "pevent-aux") == 0) {
		char *offset, *size, *flags;

		offset = strtok(payload, " ,");
		if (!offset) {
			yasm_print_err(p->y, "offset missing", -err_parse);
			return -err_parse;
		}

		size = strtok(NULL, " ,");
		if (!size) {
			yasm_print_err(p->y, "size missing", -err_parse);
			return -err_parse;
		}

		flags = strtok(NULL, " ,");
		if (!flags) {
			yasm_print_err(p->y, "flags missing", -err_parse);
			return -err_parse;
		}

		return pevent_aux(p, offset, size, flags);
	} else if (strcmp(directive, "pevent-itrace-start") == 0) {
		char *pid, *tid;

		pid = strtok(payload, " ,");
		if (!pid) {
			yasm_print_err(p->y, "pid missing", -err_parse);
			return -err_parse;
		}

		tid = strtok(NULL, " ,");
		if (!tid) {
			yasm_print_err(p->y, "tid missing", -err_parse);
			return -err_parse;
		}

		return pevent_itrace_start(p, pid, tid);
	} else if (strcmp(directive, "pevent-lost-samples") == 0) {
		char *lost;

		lost = strtok(payload, " ,");
		if (!lost) {
			yasm_print_err(p->y, "lost missing", -err_parse);
			return -err_parse;
		}

		return pevent_lost_samples(p, lost);
	} else if (strcmp(directive, "pevent-switch.in") == 0)
		return pevent_switch(p, 0u, payload);
	else if (strcmp(directive, "pevent-switch.out") == 0)
		return pevent_switch(p, PERF_RECORD_MISC_SWITCH_OUT, payload);
	else if (strcmp(directive, "pevent-switch-cpu-wide.in") == 0) {
		char *pid, *tid;

		pid = strtok(payload, " ,");
		if (!pid) {
			yasm_print_err(p->y, "pid missing", -err_parse);
			return -err_parse;
		}

		tid = strtok(NULL, " ,");
		if (!tid) {
			yasm_print_err(p->y, "tid missing", -err_parse);
			return -err_parse;
		}

		return pevent_switch_cpu_wide(p, pid, tid, 0u);
	} else if (strcmp(directive, "pevent-switch-cpu-wide.out") == 0) {
		char *pid, *tid;

		pid = strtok(payload, " ,");
		if (!pid) {
			yasm_print_err(p->y, "pid missing", -err_parse);
			return -err_parse;
		}

		tid = strtok(NULL, " ,");
		if (!tid) {
			yasm_print_err(p->y, "tid missing", -err_parse);
			return -err_parse;
		}

		return pevent_switch_cpu_wide(p, pid, tid,
					      PERF_RECORD_MISC_SWITCH_OUT);
#endif /* defined(FEATURE_PEVENT) */
	} else {
		yasm_print_err(p->y, "syntax error",
			       -err_parse_unknown_directive);
		return -err_parse_unknown_directive;
	}
}

#endif /* defined(FEATURE_SIDEBAND) */

/* Processes the current directive.
 * If the encoder returns an error, a message including current file and
 * line number together with the pt error string is printed on stderr.
 *
 * Returns 0 on success; a negative enum errcode otherwise.
 * Returns -err_internal if @p or @e is the NULL pointer.
 * Returns -err_parse_missing_directive if there was a pt directive marker,
 * but no directive.
 * Returns -stop_process if the .exp directive was encountered.
 * Returns -err_pt_lib if the pt encoder returned an error.
 * Returns -err_parse if a general parsing error was encountered.
 * Returns -err_parse_unknown_directive if there was an unknown pt directive.
 */
static int p_process(struct parser *p, struct pt_encoder *e)
{
	char *directive, *tmp, *end;
	struct pt_directive *pd;
	size_t nlen;

	if (bug_on(!p))
		return -err_internal;

	pd = p->pd;
	if (!pd)
		return -err_internal;

	directive = pd->name;

	nlen = strnlen(directive, pd_len);
	if (pd_len <= nlen)
		return -err_internal;

	/* Plus 1 for termination. */
	end = directive + nlen + 1;

	/* We must have a directive. */
	if (!directive || (strcmp(directive, "") == 0))
		return yasm_print_err(p->y, "invalid syntax",
				      -err_parse_missing_directive);

	/* Check for special directives - they won't contain labels. */
	if (strcmp(directive, ".exp") == 0) {
		int errcode;

		/* this is the end of processing pt directives, so we
		 * add a p_last label to the pt directive labels.
		 */
		errcode = l_append(p->pt_labels, "eos",
				   (uint64_t) p->pt_bytes_written);
		if (errcode < 0)
			return yasm_print_err(p->y, "append label", errcode);

		return -stop_process;
	}

	/* find a label name.  */
	tmp = strchr(directive, ':');
	if (tmp) {
		char *pt_label_name;
		uint64_t x;
		int errcode, bytes_written;
		size_t limit, len;

		pt_label_name = directive;
		directive = tmp+1;
		*tmp = '\0';

		/* ignore whitespace between label and directive. */
		while (isspace(*directive))
			directive += 1;

		/* we must have a directive, not just a label. */
		if (strcmp(directive, "") == 0)
			return yasm_print_err(p->y, "invalid syntax",
					      -err_parse_missing_directive);

		/* if we can lookup a yasm label with the same name, the
		 * current pt directive label is invalid.  */
		errcode = yasm_lookup_label(p->y, &x, pt_label_name);
		if (errcode == 0)
			errcode = -err_label_not_unique;

		if (errcode != -err_no_label)
			return yasm_print_err(p->y, "label lookup",
					      errcode);

		/* if we can lookup a pt directive label with the same
		 * name, the current pt directive label is invalid.  */
		errcode = l_lookup(p->pt_labels, &x, pt_label_name);
		if (errcode == 0)
			errcode = -err_label_not_unique;

		if (errcode != -err_no_label)
			return yasm_print_err(p->y, "label lookup",
					      -err_label_not_unique);

		bytes_written = -pte_internal;
		switch (pd->kind) {
		case pdk_pt:
			bytes_written = p->pt_bytes_written;
			break;

#if defined(FEATURE_SIDEBAND)
		case pdk_sb: {
			struct sb_file *sb;

			sb = p_get_current_sbfile(p);
			if (!sb)
				return yasm_print_err(p->y, "sideband label",
						      -err_sb_missing);

			bytes_written = sb->bytes_written;
		}
			break;
#endif /* defined(FEATURE_SIDEBAND) */
		}

		if (bytes_written < 0)
			return bytes_written;

		errcode = l_append(p->pt_labels, pt_label_name,
				   (uint64_t) bytes_written);
		if (errcode < 0)
			return errcode;

		/* Update the directive name in the parser. */
		if (end <= directive)
			return -err_internal;

		limit = (size_t) ((uintptr_t) end - (uintptr_t) directive);
		len = strnlen(directive, limit);
		if (limit <= len)
			return -err_internal;

		memmove(pd->name, directive, len);
		pd->name[len] = '\0';
	}

	switch (pd->kind) {
	case pdk_pt:
		return p_process_pt(p, e);

#if defined(FEATURE_SIDEBAND)
	case pdk_sb:
		return p_process_sb(p);
#endif
	}

	return -err_internal;
}

/* Starts the parsing process.
 *
 * Returns 0 on success; a negative enum errcode otherwise.
 * Returns -err_pt_lib if the pt encoder could not be initialized.
 * Returns -err_file_write if the .pt or .exp file could not be fully
 * written.
 */
static int p_start(struct parser *p)
{
	int errcode;

	if (bug_on(!p))
		return -err_internal;

	errcode = yasm_parse(p->y);
	if (errcode < 0)
		return errcode;

	for (;;) {
		int bytes_written;
		struct pt_encoder *e;

		errcode = yasm_next_pt_directive(p->y, p->pd);
		if (errcode < 0)
			break;

		e = pt_alloc_encoder(p->conf);
		if (!e) {
			fprintf(stderr, "pt_alloc_encoder failed\n");
			errcode = -err_pt_lib;
			break;
		}

		bytes_written = p_process(p, e);

		pt_free_encoder(e);

		if (bytes_written == -stop_process) {
			errcode = p_gen_expfile(p);
			break;
		}
		if (bytes_written < 0) {
			errcode = bytes_written;
			break;
		}
		if (fwrite(p->conf->begin, 1u, (size_t) bytes_written,
			   p->ptfile)
		    != (size_t)bytes_written) {
			fprintf(stderr, "write %s failed", p->ptfilename);
			errcode = -err_file_write;
			break;
		}
	}

	/* If there is no directive left, there's nothing more to do.  */
	if (errcode == -err_no_directive)
		return 0;

	return errcode;
}

int parse(const char *pttfile, const struct pt_config *conf)
{
	int errcode;
	struct parser *p;

	p = p_alloc(pttfile, conf);
	if (!p)
		return -err_no_mem;

	errcode = p_open_files(p);
	if (errcode < 0)
		goto error;

	errcode = p_start(p);
	p_close_files(p);

error:
	p_free(p);
	return errcode;
}

int parse_empty(char *payload)
{
	if (!payload)
		return 0;

	strtok(payload, " ");
	if (!payload || *payload == '\0')
		return 0;

	return -err_parse_trailing_tokens;
}

int parse_tnt(uint64_t *tnt, uint8_t *size, char *payload)
{
	char c;

	if (bug_on(!size))
		return -err_internal;

	if (bug_on(!tnt))
		return -err_internal;

	*size = 0;
	*tnt = 0ull;

	if (!payload)
		return 0;

	while (*payload != '\0') {
		c = *payload;
		payload++;
		if (isspace(c) || c == '.')
			continue;
		*size += 1;
		*tnt <<= 1;
		switch (c) {
		case 'n':
			break;
		case 't':
			*tnt |= 1;
			break;
		default:
			return -err_parse_unknown_char;
		}
	}

	return 0;
}

static int ipc_from_uint32(enum pt_ip_compression *ipc, uint32_t val)
{
	switch (val) {
	case pt_ipc_suppressed:
	case pt_ipc_update_16:
	case pt_ipc_update_32:
	case pt_ipc_update_48:
	case pt_ipc_sext_48:
	case pt_ipc_full:
		*ipc = (enum pt_ip_compression) val;
		return 0;
	}
	return -err_parse_ipc;
}

int parse_ip(struct parser *p, uint64_t *ip, enum pt_ip_compression *ipc,
	     char *payload)
{
	uint32_t ipcval;
	int errcode;

	if (bug_on(!ip))
		return -err_internal;

	if (bug_on(!ipc))
		return -err_internal;

	*ipc = pt_ipc_suppressed;
	*ip = 0;

	payload = strtok(payload, " :");
	if (!payload || *payload == '\0')
		return -err_parse_no_args;

	errcode = str_to_uint32(payload, &ipcval, 0);
	if (errcode < 0)
		return errcode;

	errcode = ipc_from_uint32(ipc, ipcval);
	if (errcode < 0)
		return errcode;

	payload = strtok(NULL, " :");
	if (!payload)
		return -err_parse_ip_missing;

	/* can be resolved to a label?  */
	if (*payload == '%') {
		if (!p)
			return -err_internal;

		errcode = yasm_lookup_label(p->y, ip, payload + 1);
		if (errcode < 0)
			return errcode;
	} else {
		/* can be parsed as address?  */
		errcode = str_to_uint64(payload, ip, 0);
		if (errcode < 0)
			return errcode;
	}

	/* no more tokens left.  */
	payload = strtok(NULL, " ");
	if (payload)
		return -err_parse_trailing_tokens;

	return 0;
}

int parse_uint64(uint64_t *x, char *payload)
{
	int errcode;

	if (bug_on(!x))
		return -err_internal;

	payload = strtok(payload, " ,");
	if (!payload)
		return -err_parse_no_args;

	errcode = str_to_uint64(payload, x, 0);
	if (errcode < 0)
		return errcode;

	return 0;
}

int parse_uint32(uint32_t *x, char *payload)
{
	int errcode;

	if (bug_on(!x))
		return -err_internal;

	payload = strtok(payload, " ,");
	if (!payload)
		return -err_parse_no_args;

	errcode = str_to_uint32(payload, x, 0);
	if (errcode < 0)
		return errcode;

	return 0;
}

int parse_uint16(uint16_t *x, char *payload)
{
	int errcode;

	if (bug_on(!x))
		return -err_internal;

	payload = strtok(payload, " ,");
	if (!payload)
		return -err_parse_no_args;

	errcode = str_to_uint16(payload, x, 0);
	if (errcode < 0)
		return errcode;

	return 0;
}

int parse_uint8(uint8_t *x, char *payload)
{
	int errcode;

	if (bug_on(!x))
		return -err_internal;

	payload = strtok(payload, " ,");
	if (!payload)
		return -err_parse_no_args;

	errcode = str_to_uint8(payload, x, 0);
	if (errcode < 0)
		return errcode;

	return 0;
}

int parse_tma(uint16_t *ctc, uint16_t *fc, char *payload)
{
	char *endptr;
	long int i;

	if (bug_on(!ctc || !fc))
		return -err_internal;

	payload = strtok(payload, ",");
	if (!payload || *payload == '\0')
		return -err_parse_no_args;

	i = strtol(payload, &endptr, 0);
	if (payload == endptr || *endptr != '\0')
		return -err_parse_int;

	if (i > 0xffffl)
		return -err_parse_int_too_big;

	*ctc = (uint16_t)i;

	payload = strtok(NULL, " ,");
	if (!payload)
		return -err_parse_no_args;

	i = strtol(payload, &endptr, 0);
	if (payload == endptr || *endptr != '\0')
		return -err_parse_int;

	if (i > 0xffffl)
		return -err_parse_int_too_big;

	*fc = (uint16_t)i;

	/* no more tokens left.  */
	payload = strtok(NULL, " ");
	if (payload)
		return -err_parse_trailing_tokens;

	return 0;
}
