/* $Id$ */
/* Copyright (c) 2011-2022 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Devel Asm */
/* This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>. */



#include <System.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "Asm/arch.h"
#include "Asm/asm.h"
#include "arch.h"
#include "../config.h"

/* macros */
#ifndef abs
# define abs(a)		((a) >= 0 ? (a) : -(a))
#endif
#ifndef min
# define min(a, b)	((a) < (b) ? (a) : (b))
#endif


/* AsmArch */
/* private */
/* types */
struct _AsmArch
{
	AsmArchPluginHelper helper;
	Plugin * handle;
	AsmArchPluginDefinition * definition;
	AsmArchPlugin * plugin;
	size_t instructions_cnt;
	size_t prefixes_cnt;
	size_t registers_cnt;

	/* internal */
	AsmCode * code;
	off_t base;
	char const * filename;
	FILE * fp;
	char const * buffer;
	size_t buffer_cnt;
	size_t buffer_pos;
};


/* prototypes */
/* callbacks */
static char const * _arch_get_filename(AsmArch * arch);
static AsmFunction * _arch_get_function_by_id(AsmArch * arch, AsmFunctionId id);
static AsmString * _arch_get_string_by_id(AsmArch * arch, AsmStringId id);
static ssize_t _arch_peek(AsmArch * arch, void * buf, size_t size);
static ssize_t _arch_read(AsmArch * arch, void * buf, size_t size);
static ssize_t _arch_peek_buffer(AsmArch * arch, void * buf, size_t size);
static ssize_t _arch_read_buffer(AsmArch * arch, void * buf, size_t size);
static off_t _arch_seek(AsmArch * arch, off_t offset, int whence);
static off_t _arch_seek_buffer(AsmArch * arch, off_t offset, int whence);
static ssize_t _arch_write(AsmArch * arch, void const * buf, size_t size);


/* public */
/* functions */
#ifndef STANDALONE
/* arch_new */
AsmArch * arch_new(char const * name)
{
	AsmArch * a;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, name);
#endif
	if((a = object_new(sizeof(*a))) == NULL)
		return NULL;
	memset(&a->helper, 0, sizeof(a->helper));
	if((a->handle = plugin_new(LIBDIR, PACKAGE, "arch", name)) == NULL
			|| (a->definition = plugin_lookup(a->handle,
					"arch_plugin")) == NULL)
	{
		if(a->handle != NULL)
			plugin_delete(a->handle);
		object_delete(a);
		return NULL;
	}
	a->plugin = NULL;
#if 1 /* XXX should be done when initializing */
	a->instructions_cnt = 0;
	if(a->definition->instructions != NULL)
		for(; a->definition->instructions[a->instructions_cnt].name
				!= NULL; a->instructions_cnt++);
	a->prefixes_cnt = 0;
	if(a->definition->prefixes != NULL)
		for(; a->definition->prefixes[a->prefixes_cnt].name != NULL;
				a->prefixes_cnt++);
	a->registers_cnt = 0;
	if(a->definition->registers != NULL)
		for(; a->definition->registers[a->registers_cnt].name != NULL;
				a->registers_cnt++);
#endif
	a->filename = NULL;
	a->fp = NULL;
	a->buffer = NULL;
	a->buffer_cnt = 0;
	a->buffer_pos = 0;
	return a;
}
#endif


/* arch_delete */
void arch_delete(AsmArch * arch)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if(arch->handle != NULL)
		plugin_delete(arch->handle);
	object_delete(arch);
}


/* accessors */
/* arch_can_decode */
int arch_can_decode(AsmArch * arch)
{
	return (arch->definition->decode != NULL) ? 1 : 0;
}


/* arch_get_definition */
AsmArchDefinition const * arch_get_definition(AsmArch * arch)
{
	return arch->definition->definition;
}


/* arch_get_description */
char const * arch_get_description(AsmArch * arch)
{
	return arch->definition->description;
}


/* arch_get_format */
char const * arch_get_format(AsmArch * arch)
{
	if(arch->definition->definition != NULL
			&& arch->definition->definition->format != NULL)
		return arch->definition->definition->format;
	return NULL;
}


/* arch_get_instruction */
AsmArchInstruction const * arch_get_instruction(AsmArch * arch, size_t index)
{
	if(index >= arch->instructions_cnt)
		return NULL;
	return &arch->definition->instructions[index];
}


/* arch_get_instruction_by_name */
AsmArchInstruction const * arch_get_instruction_by_name(AsmArch * arch,
		char const * name)
{
	size_t i;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(arch, \"%s\")\n", __func__, name);
#endif
	for(i = 0; i < arch->instructions_cnt; i++)
		if(strcmp(arch->definition->instructions[i].name, name) == 0)
			return &arch->definition->instructions[i];
	return NULL;
}


/* arch_get_instruction_by_opcode */
AsmArchInstruction const * arch_get_instruction_by_opcode(AsmArch * arch,
		uint8_t size, uint32_t opcode)
{
	size_t i;
	AsmArchInstruction const * ai;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(arch, %u, 0x%x)\n", __func__, size, opcode);
#endif
	for(i = 0; i < arch->instructions_cnt; i++)
	{
		ai = &arch->definition->instructions[i];
		if(AO_GET_SIZE(ai->flags) != size)
			continue;
		if(ai->opcode == opcode)
			return ai;
	}
	return NULL;
}


/* arch_get_instruction_by_call */
static int _call_operands(AsmArch * arch,
		AsmArchInstruction const * instruction,
		AsmArchInstructionCall * call);
static int _call_operands_constant(AsmArchOperandDefinition definition,
		AsmArchOperand * operand);
static int _call_operands_dregister(AsmArch * arch,
		AsmArchOperandDefinition definition, AsmArchOperand * operand);
static int _call_operands_immediate(AsmArchOperandDefinition definition,
		AsmArchOperand * operand);
static int _call_operands_register(AsmArch * arch,
		AsmArchOperandDefinition definition, AsmArchOperand * operand);
static AsmArchInstruction const * _call_update(
		AsmArchInstruction const * instruction,
		AsmArchInstructionCall * call);

AsmArchInstruction const * arch_get_instruction_by_call(AsmArch * arch,
		AsmArchInstructionCall * call)
{
	size_t i;
	AsmArchInstruction const * ai;
	int found = 0;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, call->name);
#endif
	for(i = 0; i < arch->instructions_cnt; i++)
	{
		ai = &arch->definition->instructions[i];
		/* FIXME use a (sorted) hash table */
		if(strcmp(ai->name, call->name) != 0)
			continue;
		found = 1;
		if(_call_operands(arch, ai, call) == 0)
			return _call_update(ai, call);
	}
	error_set_code(1, "%s \"%s\"", found ? "Invalid arguments to"
			: "Unknown instruction", call->name);
	return NULL;
}

static int _call_operands(AsmArch * arch,
		AsmArchInstruction const * instruction,
		AsmArchInstructionCall * call)
{
	size_t i;
	AsmArchOperandDefinition definition;
	AsmArchOperand * operand;

	if(call->operands_cnt == 0 && AO_GET_TYPE(instruction->op1) != AOT_NONE)
		return -1;
	for(i = 0; i < call->operands_cnt; i++)
	{
		switch(i)
		{
			case 0:	definition = instruction->op1; break;
			case 1:	definition = instruction->op2; break;
			case 2:	definition = instruction->op3; break;
			case 3:	definition = instruction->op4; break;
			case 4:	definition = instruction->op5; break;
			default:
				return -1;
		}
		operand = &call->operands[i];
#ifdef DEBUG
		fprintf(stderr, "DEBUG: %s() operand %lu, type %u, type %u\n",
				__func__, i, AO_GET_TYPE(definition),
				AO_GET_TYPE(operand->definition));
#endif
		if(AO_GET_TYPE(definition) == AOT_CONSTANT)
		{
			if(AO_GET_TYPE(operand->definition) != AOT_IMMEDIATE)
				return -1;
		}
		else if(AO_GET_TYPE(definition)
				!= AO_GET_TYPE(operand->definition))
			return -1;
		switch(AO_GET_TYPE(definition))
		{
			case AOT_CONSTANT:
				if(_call_operands_constant(definition, operand)
						!= 0)
					return -1;
				break;
			case AOT_IMMEDIATE:
				if(_call_operands_immediate(definition, operand)
						!= 0)
					return -1;
				break;
			case AOT_DREGISTER:
				if(_call_operands_dregister(arch, definition,
							operand) != 0)
					return -1;
				break;
			case AOT_REGISTER:
				if(_call_operands_register(arch, definition,
							operand) != 0)
					return -1;
				break;
		}
	}
	return 0;
}

static int _call_operands_constant(AsmArchOperandDefinition definition,
		AsmArchOperand * operand)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s() %u %lu\n", __func__,
			AO_GET_VALUE(definition),
			operand->value.immediate.value);
#endif
	if(AO_GET_VALUE(definition) != operand->value.immediate.value)
		return -1;
	/* set this operand as a constant */
	operand->definition &= AOM_TYPE;
	operand->definition |= (AOT_CONSTANT << AOD_TYPE);
	return 0;
}

static int _call_operands_dregister(AsmArch * arch,
		AsmArchOperandDefinition definition, AsmArchOperand * operand)
{
	uint64_t offset;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s() %ld\n", __func__,
			operand->value.dregister.offset);
#endif
	if(_call_operands_register(arch, definition, operand) != 0)
		return -1;
	/* check if there is an offset applied */
	if(operand->value.dregister.offset == 0)
		return 0;
	/* check if the offset fits */
	offset = abs(operand->value.dregister.offset);
	offset >>= AO_GET_DSIZE(definition);
	if(offset > 0)
		return -1;
	return 0;
}

static int _call_operands_immediate(AsmArchOperandDefinition opdefinition,
		AsmArchOperand * operand)
{
	uint64_t value;
	uint32_t size;

	/* check if the size fits */
	value = operand->value.immediate.value;
#if 0 /* XXX ignore for now */
	if((size = AO_GET_SIZE(opdefinition)) > 0
			&& AO_GET_FLAGS(opdefinition) & AOF_SIGNED)
		size--;
#else
	size = AO_GET_SIZE(opdefinition);
#endif
	value >>= size;
	if(value > 0)
		return -1;
	/* check if it is signed */
	if(operand->value.immediate.negative
			&& !(AO_GET_FLAGS(opdefinition) & AOF_SIGNED))
		return -1;
	return 0;
}

static int _call_operands_register(AsmArch * arch,
		AsmArchOperandDefinition opdefinition, AsmArchOperand * operand)
{
	char const * name = operand->value._register.name;
	AsmArchDefinition const * definition;
	uint32_t size;
	AsmArchRegister const * ar;

	/* obtain the size */
	if((definition = arch->definition->definition) != NULL
			&& definition->instruction_size != 0)
		size = definition->instruction_size;
	else
		size = AO_GET_SIZE(opdefinition);
	/* check if it exists */
	if((ar = arch_get_register_by_name_size(arch, name, size)) == NULL)
		return -1;
	/* for implicit instructions it must match */
	if(AO_GET_FLAGS(opdefinition) & AOF_IMPLICIT
			&& AO_GET_VALUE(opdefinition) != ar->id)
		return -1;
	return 0;
}

static AsmArchInstruction const * _call_update(
		AsmArchInstruction const * instruction,
		AsmArchInstructionCall * call)
{
	size_t i;

	for(i = 0; i < call->operands_cnt; i++)
		switch(i)
		{
			case 0:
				call->operands[i].definition = instruction->op1;
				break;
			case 1:
				call->operands[i].definition = instruction->op2;
				break;
			case 2:
				call->operands[i].definition = instruction->op3;
				break;
			case 3:
				call->operands[i].definition = instruction->op4;
				break;
			case 4:
				call->operands[i].definition = instruction->op5;
				break;
			default:
				return NULL;
		}
	return instruction;
}


/* arch_get_instructions */
AsmArchInstruction const * arch_get_instructions(AsmArch * arch)
{
	return arch->definition->instructions;
}


/* arch_get_name */
char const * arch_get_name(AsmArch * arch)
{
	return arch->definition->name;
}


/* arch_get_prefix */
AsmArchPrefix const * arch_get_prefix(AsmArch * arch, size_t index)
{
	if(index >= arch->prefixes_cnt)
		return NULL;
	return &arch->definition->prefixes[index];
}


/* arch_get_prefix_by_call */
AsmArchPrefix const * arch_get_prefix_by_call(AsmArch * arch,
		AsmArchInstructionCall * call)
{
	if(call->prefix == NULL)
		return NULL;
	return arch_get_prefix_by_name(arch, call->prefix);
}


/* arch_get_prefix_by_name */
AsmArchPrefix const * arch_get_prefix_by_name(AsmArch * arch,
		char const * name)
{
	size_t i;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\") %zu\n", __func__, name,
			arch->prefixes_cnt);
#endif
	for(i = 0; i < arch->prefixes_cnt; i++)
		if(strcmp(arch->definition->prefixes[i].name, name) == 0)
			return &arch->definition->prefixes[i];
	return NULL;
}


/* arch_get_prefix_by_opcode */
AsmArchPrefix const * arch_get_prefix_by_opcode(AsmArch * arch, uint8_t size,
		uint32_t opcode)
{
	size_t i;
	AsmArchOperandDefinition flags;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\") %zu\n", __func__, name,
			arch->prefixes_cnt);
#endif
	for(i = 0; i < arch->prefixes_cnt; i++)
		if(arch->definition->prefixes[i].opcode == opcode)
		{
			if(size != 0)
			{
				flags = arch->definition->prefixes[i].flags;
				if(AO_GET_SIZE(flags) != size)
					continue;
			}
			return &arch->definition->prefixes[i];
		}
	return NULL;
}


/* arch_get_prefixes */
AsmArchPrefix const * arch_get_prefixes(AsmArch * arch)
{
	return arch->definition->prefixes;
}


/* arch_get_register */
AsmArchRegister const * arch_get_register(AsmArch * arch, size_t index)
{
	if(index >= arch->registers_cnt)
		return NULL;
	return &arch->definition->registers[index];
}


/* arch_get_register_by_id_size */
AsmArchRegister const * arch_get_register_by_id_size(AsmArch * arch,
		uint32_t id, uint32_t size)
{
	size_t i;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%u, %u)\n", __func__, id, size);
#endif
	for(i = 0; i < arch->registers_cnt; i++)
		if(arch->definition->registers[i].id == id
				&& arch->definition->registers[i].size == size)
			return &arch->definition->registers[i];
	return NULL;
}


/* arch_get_register_by_name */
AsmArchRegister const * arch_get_register_by_name(AsmArch * arch,
		char const * name)
{
	size_t i;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, name);
#endif
	for(i = 0; i < arch->registers_cnt; i++)
		if(strcmp(arch->definition->registers[i].name, name) == 0)
			return &arch->definition->registers[i];
	return NULL;
}


/* arch_get_register_by_name_size */
AsmArchRegister const * arch_get_register_by_name_size(AsmArch * arch,
		char const * name, uint32_t size)
{
	size_t i;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\", %u)\n", __func__, name, size);
#endif
	for(i = 0; i < arch->registers_cnt; i++)
		if(arch->definition->registers[i].size != size)
			continue;
		else if(strcmp(arch->definition->registers[i].name, name) == 0)
			return &arch->definition->registers[i];
	return NULL;
}


/* arch_get_registers */
AsmArchRegister const * arch_get_registers(AsmArch * arch)
{
	return arch->definition->registers;
}


/* useful */
/* arch_decode */
int arch_decode(AsmArch * arch, AsmCode * code, off_t base,
		AsmArchInstructionCall ** calls, size_t * calls_cnt)
{
	int ret = 0;
	AsmArchInstructionCall * c;
	size_t c_cnt;
	AsmArchInstructionCall * p;
	size_t offset = 0;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%ld)\n", __func__, base);
#endif
	if(arch->definition->decode == NULL)
		return -error_set_code(1, "%s: %s", arch->definition->name,
				"Disassembly not supported");
	/* check the arguments */
	if(calls == NULL || calls_cnt == NULL)
		return -error_set_code(1, "%s: %s", arch->definition->name,
				strerror(EINVAL));
	c = *calls;
	c_cnt = *calls_cnt;
	arch->code = code;
	for(;;)
	{
		if((p = realloc(c, sizeof(*c) * (c_cnt + 1))) == NULL)
		{
			free(c);
			ret = -error_set_code(1, "%s", strerror(errno));
			break;
		}
		c = p;
		p = &c[c_cnt];
		memset(p, 0, sizeof(*p));
		p->base = base + offset;
		p->offset = arch->buffer_pos;
		if(arch->definition->decode(arch->plugin, p) != 0)
			break;
		p->size = arch->buffer_pos - p->offset;
		offset += p->size;
		c_cnt++;
	}
	*calls = c;
	*calls_cnt = c_cnt;
	arch->code = NULL;
	return ret;
}


/* arch_decode_at */
int arch_decode_at(AsmArch * arch, AsmCode * code, off_t offset, size_t size,
		off_t base, AsmArchInstructionCall ** calls, size_t * calls_cnt)
{
	int ret;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%ld, %lu, %ld)\n", __func__, offset, size,
			base);
#endif
	/* FIXME this only works for files */
	if(arch->fp == NULL)
		return -error_set_code(1, "%s", strerror(ENOSYS));
	if(fseek(arch->fp, offset, SEEK_SET) != 0)
		return -error_set_code(1, "%s", strerror(errno));
	if(size == 0)
		return 0;
	arch->code = code;
	arch->buffer_pos = offset;
	arch->buffer_cnt = offset + size;
	if((ret = arch_decode(arch, code, base, calls, calls_cnt)) == 0
			&& fseek(arch->fp, offset + size, SEEK_SET) != 0)
	{
		free(*calls); /* XXX the pointer was updated anyway... */
		ret = -error_set_code(1, "%s", strerror(errno));
	}
	return ret;
}


/* arch_encode */
int arch_encode(AsmArch * arch, AsmArchPrefix const * prefix,
		AsmArchInstruction const * instruction,
		AsmArchInstructionCall * call)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, instruction->name);
#endif
	return arch->definition->encode(arch->plugin, prefix, instruction, call);
}


/* arch_exit */
int arch_exit(AsmArch * arch)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if(arch->plugin != NULL)
		arch->definition->destroy(arch->plugin);
	arch->plugin = NULL;
	arch->filename = NULL;
	arch->fp = NULL;
	arch->buffer = NULL;
	arch->buffer_cnt = 0;
	arch->buffer_pos = 0;
	memset(&arch->helper, 0, sizeof(arch->helper));
	return 0;
}


/* arch_init */
int arch_init(AsmArch * arch, char const * filename, FILE * fp)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\", %p)\n", __func__, filename,
			(void *)fp);
#endif
	if(arch->plugin != NULL)
		arch_exit(arch);
	arch->base = 0;
	arch->filename = filename;
	arch->fp = fp;
	arch->buffer = NULL;
	arch->buffer_cnt = 0;
	arch->buffer_pos = 0; /* XXX used as offset */
	arch->helper.arch = arch;
	arch->helper.get_filename = _arch_get_filename;
	arch->helper.get_function_by_id = _arch_get_function_by_id;
	arch->helper.get_prefix_by_opcode = arch_get_prefix_by_opcode;
	arch->helper.get_instruction_by_opcode = arch_get_instruction_by_opcode;
	arch->helper.get_register_by_id_size = arch_get_register_by_id_size;
	arch->helper.get_register_by_name_size = arch_get_register_by_name_size;
	arch->helper.get_string_by_id = _arch_get_string_by_id;
	arch->helper.peek = _arch_peek;
	arch->helper.read = _arch_read;
	arch->helper.seek = _arch_seek;
	arch->helper.write = _arch_write;
	if(arch->definition->init != NULL
			&& (arch->plugin = arch->definition->init(
					&arch->helper)) == NULL)
		return -1;
	return 0;
}


/* arch_init */
int arch_init_buffer(AsmArch * arch, char const * buffer, size_t size)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if(arch->plugin != NULL)
		arch_exit(arch);
	arch->base = 0;
	arch->filename = "buffer";
	arch->fp = NULL;
	arch->buffer = buffer;
	arch->buffer_cnt = size;
	arch->buffer_pos = 0;
	arch->helper.arch = arch;
	arch->helper.get_filename = _arch_get_filename;
	arch->helper.get_function_by_id = _arch_get_function_by_id;
	arch->helper.get_prefix_by_opcode = arch_get_prefix_by_opcode;
	arch->helper.get_instruction_by_opcode = arch_get_instruction_by_opcode;
	arch->helper.get_register_by_id_size = arch_get_register_by_id_size;
	arch->helper.get_register_by_name_size = arch_get_register_by_name_size;
	arch->helper.get_string_by_id = _arch_get_string_by_id;
	arch->helper.write = NULL;
	arch->helper.peek = _arch_peek_buffer;
	arch->helper.read = _arch_read_buffer;
	arch->helper.seek = _arch_seek_buffer;
	if(arch->definition->init != NULL
			&& (arch->plugin = arch->definition->init(
					&arch->helper)) == NULL)
		return -1;
	return 0;
}


/* arch_read */
ssize_t arch_read(AsmArch * arch, void * buf, size_t size)
{
	if(arch->helper.read == NULL)
		return -error_set_code(1, "%s", "read: No helper defined");
	return arch->helper.read(arch, buf, size);
}


/* arch_seek */
off_t arch_seek(AsmArch * arch, off_t offset, int whence)
{
	if(arch->helper.seek == NULL)
		return -error_set_code(1, "%s", "seek: No helper defined");
	return arch->helper.seek(arch, offset, whence);
}


/* private */
/* callbacks */
/* arch_get_filename */
static char const * _arch_get_filename(AsmArch * arch)
{
	return arch->filename;
}


/* arch_get_function_by_id */
static AsmFunction * _arch_get_function_by_id(AsmArch * arch, AsmFunctionId id)
{
	return asmcode_get_function_by_id(arch->code, id);
}


/* arch_get_string_by_id */
static AsmString * _arch_get_string_by_id(AsmArch * arch, AsmStringId id)
{
	return asmcode_get_string_by_id(arch->code, id);
}


/* arch_peek */
static ssize_t _arch_peek(AsmArch * arch, void * buf, size_t size)
{
	ssize_t s;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(arch, %p, %lu)\n", __func__, buf, size);
#endif
	if((s = _arch_read(arch, buf, size)) == -1)
		return -1;
	if(_arch_seek(arch, -s, SEEK_CUR) == -1)
		return -1;
	return s;
}


/* arch_peek_buffer */
static ssize_t _arch_peek_buffer(AsmArch * arch, void * buf, size_t size)
{
	ssize_t s;

	if((s = _arch_read_buffer(arch, buf, size)) == -1)
		return -1;
	if(_arch_seek_buffer(arch, -s, SEEK_CUR) == -1)
		return -1;
	return s;
}


/* arch_read */
static ssize_t _arch_read(AsmArch * arch, void * buf, size_t size)
{
	size_t s = min(arch->buffer_cnt - arch->buffer_pos, size);

	if(fread(buf, s, 1, arch->fp) == 1)
	{
		arch->buffer_pos += s;
		return s;
	}
	if(ferror(arch->fp))
		return -error_set_code(1, "%s: %s", arch->filename,
				strerror(errno));
	if(feof(arch->fp))
		return -error_set_code(1, "%s: %s", arch->filename,
				"End of file reached");
	return -error_set_code(1, "%s: %s", arch->filename, "Read error");
}


/* arch_read_buffer */
static ssize_t _arch_read_buffer(AsmArch * arch, void * buf, size_t size)
{
	ssize_t s = min(arch->buffer_cnt - arch->buffer_pos, size);

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if(s == 0)
		return -error_set_code(1, "%s", "End of buffer reached");
	memcpy(buf, &arch->buffer[arch->buffer_pos], s);
	arch->buffer_pos += s;
	return s;
}


/* arch_seek */
static off_t _arch_seek(AsmArch * arch, off_t offset, int whence)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(arch, %ld, %d)\n", __func__, offset, whence);
#endif
	if(fseek(arch->fp, offset, whence) != 0)
		return -error_set_code(1, "%s: %s", arch->filename, strerror(
					errno));
	arch->buffer_pos = ftello(arch->fp);
	return arch->buffer_pos;
}


/* arch_seek_buffer */
static off_t _arch_seek_buffer(AsmArch * arch, off_t offset, int whence)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(arch, %ld, %d)\n", __func__, offset, whence);
#endif
	if(whence == SEEK_SET)
	{
		if(offset < 0 || (size_t)offset >= arch->buffer_cnt)
			return -error_set_code(1, "%s", "Invalid seek");
		arch->buffer_pos = offset;
	}
	else if(whence == SEEK_CUR)
	{
		if(offset < 0)
		{
			if((size_t)(-offset) > arch->buffer_pos)
				return -error_set_code(1, "%s", "Invalid seek");
		}
		else if(offset > 0)
		{
			if((size_t)offset + arch->buffer_pos
					>= arch->buffer_cnt)
				return -error_set_code(1, "%s", "Invalid seek");
		}
		arch->buffer_pos += offset;
	}
	else
		/* FIXME implement */
		return -error_set_code(1, "%s", "Not implemented");
	return arch->buffer_pos;
}


/* arch_write */
static ssize_t _arch_write(AsmArch * arch, void const * buf, size_t size)
{
	if(fwrite(buf, size, 1, arch->fp) == 1)
		return size;
	if(ferror(arch->fp))
		return -error_set_code(1, "%s: %s", arch->filename,
				strerror(errno));
	if(feof(arch->fp))
		return -error_set_code(1, "%s: %s", arch->filename,
				"End of file reached");
	return -error_set_code(1, "%s: %s", arch->filename, "Write error");
}
