/* $Id: elf.c,v 1.2 2012/08/30 23:48:56 khorben Exp $ */
/* Copyright (c) 2007 khorben of Uberwall */
/* This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */



#ifdef __OpenBSD__
# include <elf_abi.h>
#else
# include <elf.h>
#endif
#include "plugin.h"


/* elf */
/* public */
/* types */
typedef union
{
	Elf32_Ehdr e32;
	Elf64_Ehdr e64;
} elf_ehdr;


/* constants */
/* elf.h might not know these (GNU/Linux) */
#ifndef ELFOSABI_HURD
# define ELFOSABI_HURD 4
#endif
#ifndef ELFOSABI_MONTEREY
# define ELFOSABI_MONTEREY 7
#endif
#ifndef EM_486
# define EM_486 6
#endif
#ifndef EM_MIPS_RS3_LE
# define EM_MIPS_RS3_LE 10
#endif


/* variables */
/* magic */
static unsigned char elf[] = ELFMAG;

PluginMagic elf_magic[] =
{
	{ sizeof(Elf32_Ehdr),	0,	elf,	sizeof(elf)-1	},
	{ 0,			0,	NULL,	0		}
};


/* functions */
static int elf_callback(PluginHelper * ph, int signature, FILE * fp);


/* plugin */
Plugin plugin =
{
	PT_EXECUTABLE,
	"ELF",
	elf_magic,
	elf_callback
};


/* private */
/* functions */
/* plugin_callback */
static int _callback_32(PluginHelper * ph, Elf32_Ehdr * hdr);
static int _callback_64(PluginHelper * ph, Elf64_Ehdr * hdr);

static int elf_callback(PluginHelper * ph, int signature, FILE * fp)
{
	fpos_t pos;
	elf_ehdr buf;

	if(fgetpos(fp, &pos) != 0)
		return -1;
	if(fread(&buf, sizeof(buf), 1, fp) == 1)
	{
		if(buf.e32.e_ident[EI_CLASS] == ELFCLASS32)
			return _callback_32(ph, &buf.e32);
		if(buf.e64.e_ident[EI_CLASS] == ELFCLASS64)
			return _callback_64(ph, &buf.e64);
		return -1;
	}
	if(fsetpos(fp, &pos) != 0)
		return -1;
	if(fread(&buf.e32, sizeof(buf.e32), 1, fp) == 1
			&& buf.e32.e_ident[EI_CLASS] == ELFCLASS32)
		return _callback_32(ph, &buf.e32);
	return -1;
}

static int _callback_encoding(PluginHelper * ph, unsigned char encoding);
static int _callback_version(PluginHelper * ph, unsigned char version);
static int _callback_system(PluginHelper * ph, unsigned char system);
static void _32_endian(Elf32_Ehdr * hdr);
static int _callback_type(PluginHelper * ph, uint16_t type);
static int _callback_machine(PluginHelper * ph, uint16_t machine);
static int _callback_32(PluginHelper * ph, Elf32_Ehdr * hdr)
{
	int score = 0;

	ph->printf(ph, "%s", "32-bits");
	score+=_callback_encoding(ph, hdr->e_ident[EI_DATA]);
	score+=_callback_version(ph, hdr->e_ident[EI_VERSION]);
	score+=_callback_system(ph, hdr->e_ident[EI_OSABI]);
	_32_endian(hdr);
	score+=_callback_type(ph, hdr->e_type);
	score+=_callback_machine(ph, hdr->e_machine);
	ph->printf(ph, "\n");
	return score / 5;
}

static void _32_endian(Elf32_Ehdr * hdr)
{
	if(hdr->e_ident[EI_DATA] == ELFDATA2LSB)
	{
		hdr->e_type = htol16(hdr->e_type);
		hdr->e_machine = htol16(hdr->e_machine);
		hdr->e_version = htol32(hdr->e_version);
	}
	else if(hdr->e_ident[EI_DATA] == ELFDATA2MSB)
	{
		hdr->e_type = htob16(hdr->e_type);
		hdr->e_machine = htob16(hdr->e_machine);
		hdr->e_version = htob32(hdr->e_version);
	}
}

static void _64_endian(Elf64_Ehdr * hdr);
static int _callback_64(PluginHelper * ph, Elf64_Ehdr * hdr)
{
	int score = 0;

	ph->printf(ph, "%s", "64-bits");
	score+=_callback_encoding(ph, hdr->e_ident[EI_DATA]);
	score+=_callback_version(ph, hdr->e_ident[EI_VERSION]);
	score+=_callback_system(ph, hdr->e_ident[EI_OSABI]);
	_64_endian(hdr);
	score+=_callback_type(ph, hdr->e_type);
	score+=_callback_machine(ph, hdr->e_machine);
	ph->printf(ph, "\n");
	return score / 5;
}

static void _64_endian(Elf64_Ehdr * hdr)
{
	if(hdr->e_ident[EI_DATA] == ELFDATA2LSB)
	{
		hdr->e_type = htol16(hdr->e_type);
		hdr->e_machine = htol16(hdr->e_machine);
		hdr->e_version = htol32(hdr->e_version);
	}
	else if(hdr->e_ident[EI_DATA] == ELFDATA2MSB)
	{
		hdr->e_type = htob16(hdr->e_type);
		hdr->e_machine = htob16(hdr->e_machine);
		hdr->e_version = htob32(hdr->e_version);
	}
}

static int _callback_encoding(PluginHelper * ph, unsigned char encoding)
{
	switch(encoding)
	{
		case ELFDATANONE:
			ph->printf(ph, "%s", ", invalid encoding");
			return 50;
		case ELFDATA2LSB:
			ph->printf(ph, "%s", ", little endian");
			return 100;
		case ELFDATA2MSB:
			ph->printf(ph, "%s", ", big endian");
			return 100;
		default:
			ph->printf(ph, "%s", ", unknown encoding");
			return 0;
	}
}

static int _callback_version(PluginHelper * ph, unsigned char version)
{
	if(version == EV_CURRENT)
	{
		ph->printf(ph, "%s", ", current version");
		return 100;
	}
	ph->printf(ph, "%s", ", invalid version");
	return 0;
}

static int _callback_system(PluginHelper * ph, unsigned char system)
{
	struct
	{
		unsigned char system;
		char * name;
		int score;
	} sns[] =
	{
		{ ELFOSABI_SYSV,	"SYSV",		100	},
		{ ELFOSABI_HPUX,	"HP-UX",	100	},
		{ ELFOSABI_NETBSD,	"NetBSD",	100	},
		{ ELFOSABI_LINUX,	"GNU/Linux",	100	},
		{ ELFOSABI_HURD,	"GNU/Hurd",	100	},
		{ ELFOSABI_SOLARIS,	"Solaris",	100	},
		{ ELFOSABI_MONTEREY,	"Monterey",	100	},
		{ ELFOSABI_IRIX,	"IRIX",		100	},
		{ ELFOSABI_FREEBSD,	"FreeBSD",	100	},
		{ ELFOSABI_TRU64,	"TRU64",	100	},
		{ ELFOSABI_MODESTO,	"Modesto",	100	},
		{ ELFOSABI_OPENBSD,	"OpenBSD",	100	},
		{ ELFOSABI_ARM,		"ARM",		100	},
		{ 0,			NULL,		0	}
	};
	size_t i;

	for(i = 0; sns[i].name != NULL; i++)
		if(sns[i].system == system)
			break;
	if(sns[i].name == NULL)
	{
		ph->printf(ph, "%s%d%s", ", unknown ABI (", system, ")");
		return 0;
	}
	ph->printf(ph, "%s%s%s", ", ", sns[i].name, " ABI");
	return sns[i].score;
}

static int _callback_type(PluginHelper * ph, uint16_t type)
{
	struct
	{
		uint16_t type;
		char * name;
		int score;
	} tns[] =
	{
		{ ET_NONE,	"no type",		50	},
		{ ET_REL,	"relocatable",		100	},
		{ ET_EXEC,	"executable",		100	},
		{ ET_DYN,	"shared object",	100	},
		{ ET_CORE,	"core file",		100	},
		{ 0,		NULL,			0	}
	};
	size_t i;

	for(i = 0; tns[i].name != NULL; i++)
		if(tns[i].type == type)
			break;
	if(tns[i].name == NULL)
	{
		ph->printf(ph, "%s", ", unknown type");
		return 0;
	}
	ph->printf(ph, "%s%s", ", ", tns[i].name);
	return tns[i].score;
}

static int _callback_machine(PluginHelper * ph, uint16_t machine)
{
	struct
	{
		uint16_t machine;
		char * name;
		int score;
	} mns[] =
	{
		{ EM_NONE,		"no machine",		50	},
		{ EM_SPARC,		"SPARC",		100	},
		{ EM_386,		"i386",			100	},
		{ EM_68K,		"m68k",			100	},
		{ EM_88K,		"m88k",			100	},
		{ EM_486,		"i486",			100	},
		{ EM_860,		"i860",			100	},
		{ EM_MIPS,		"MIPS",			100	},
		{ EM_MIPS_RS3_LE,	"MIPS",			100	},
		{ EM_ARM,		"ARM",			100	},
		{ EM_X86_64,		"amd64",		100	},
		{ 0,			NULL,			0	}
	};
	size_t i;

	for(i = 0; mns[i].name != NULL; i++)
		if(mns[i].machine == machine)
			break;
	if(mns[i].name == NULL)
	{
		ph->printf(ph, "%s%d%s", ", unknown target (", machine, ")");
		return 0;
	}
	ph->printf(ph, "%s%s", ", for ", mns[i].name);
	return mns[i].score;
}