/*
 * create-kpatch-module.c
 *
 * 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; either version 2
 * of the License, or (at your option) any later version.
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA,
 * 02110-1301, USA.
 */

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

#include "log.h"
#include "kpatch-elf.h"
#include "kpatch-intermediate.h"
#include "kpatch-patch.h"

/* For log.h */
char *childobj;
enum loglevel loglevel = NORMAL;

/*
 * Create .kpatch.dynrelas from .kpatch.relocations and .kpatch.symbols sections
 *
 * Iterate through .kpatch.relocations and fill in the corresponding dynrela
 * entry using information from .kpatch.relocations and .kpatch.symbols
 */
static void create_dynamic_rela_sections(struct kpatch_elf *kelf, struct section *krelasec,
					 struct section *ksymsec, struct section *strsec)
{
	struct kpatch_patch_dynrela *dynrelas;
	struct kpatch_relocation *krelas;
	struct kpatch_symbol *ksym, *ksyms;
	struct section *dynsec;
	struct symbol *sym;
	struct rela *rela;
	unsigned int index, nr, offset, dest_offset, objname_offset, name_offset;
	unsigned int type;
	long addend;
	char *target_name;

	ksyms = ksymsec->data->d_buf;
	krelas = krelasec->data->d_buf;
	nr = (unsigned int)(krelasec->data->d_size / sizeof(*krelas));

	dynsec = create_section_pair(kelf, ".kpatch.dynrelas", sizeof(*dynrelas), nr);
	dynrelas = dynsec->data->d_buf;

	for (index = 0; index < nr; index++) {
		offset = index * (unsigned int)sizeof(*krelas);

		/*
		 * To fill in each dynrela entry, find dest location,
		 * objname offset, ksym, and symbol name offset
		 */

		/* Get dest location */
		rela = find_rela_by_offset(krelasec->rela,
					   offset + offsetof(struct kpatch_relocation, dest));
		if (!rela)
			ERROR("find_rela_by_offset");
		sym = rela->sym;
		dest_offset = (unsigned int)rela->addend;

		/* Get objname offset */
		rela = find_rela_by_offset(krelasec->rela,
			(unsigned int)(offset + offsetof(struct kpatch_relocation, objname)));
		if (!rela)
			ERROR("find_rela_by_offset");
		objname_offset = (unsigned int)rela->addend;

		/* Get ksym (.kpatch.symbols entry) and symbol name offset */
		rela = find_rela_by_offset(krelasec->rela,
			(unsigned int)(offset + offsetof(struct kpatch_relocation, ksym)));
		if (!rela)
			ERROR("find_rela_by_offset");
		ksym = ksyms + (rela->addend / sizeof(*ksyms));

		offset = (unsigned int )(index * sizeof(*ksyms));
		rela = find_rela_by_offset(ksymsec->rela,
			(unsigned int)(offset + offsetof(struct kpatch_symbol, name)));
		if (!rela)
			ERROR("find_rela_by_offset");
		name_offset = (unsigned int)rela->addend;

		/* Fill in dynrela entry */
		type = krelas[index].type;
		addend = krelas[index].addend;
		if (type == R_X86_64_64 && (addend > INT_MAX || addend <= INT_MIN)) {
			target_name = (char *)strsec->data->d_buf + name_offset;
			ERROR("got R_X86_64_64 dynrela for '%s' with addend too large or too small for an int: %lx",
				target_name, addend);
		}

		dynrelas[index].src = ksym->src;
		dynrelas[index].addend = addend;
		dynrelas[index].type = type;
		dynrelas[index].external = krelas[index].external;
		dynrelas[index].sympos = ksym->sympos;

		/* dest */
		ALLOC_LINK(rela, &dynsec->rela->relas);
		rela->sym = sym;
		rela->type = R_X86_64_64;
		rela->addend = dest_offset;
		rela->offset = (unsigned int)(index * sizeof(*dynrelas));

		/* name */
		ALLOC_LINK(rela, &dynsec->rela->relas);
		rela->sym = strsec->secsym;
		rela->type = R_X86_64_64;
		rela->addend = name_offset;
		rela->offset = (unsigned int)(index * sizeof(*dynrelas) + \
					      offsetof(struct kpatch_patch_dynrela, name));

		/* objname */
		ALLOC_LINK(rela, &dynsec->rela->relas);
		rela->sym = strsec->secsym;
		rela->type = R_X86_64_64;
		rela->addend = objname_offset;
		rela->offset = (unsigned int)(index * sizeof(*dynrelas) + \
					      offsetof(struct kpatch_patch_dynrela, objname));
	}
}

static void remove_intermediate_sections(struct kpatch_elf *kelf)
{
	size_t i;
	char *intermediate_sections[] = {
		".kpatch.symbols",
		".rela.kpatch.symbols",
		".kpatch.relocations",
		".rela.kpatch.relocations",
		".kpatch.arch",
		".rela.kpatch.arch"
	};

	for (i = 0; i < sizeof(intermediate_sections)/sizeof(intermediate_sections[0]); i++)
		kpatch_remove_and_free_section(kelf, intermediate_sections[i]);
}

struct arguments {
	char *args[2];
	int debug;
};

static char args_doc[] = "input.o output.o";

static struct argp_option options[] = {
	{"debug", 'd', 0, 0, "Show debug output" },
	{ 0 }
};

static error_t parse_opt (int key, char *arg, struct argp_state *state)
{
	/* Get the input argument from argp_parse, which we
	   know is a pointer to our arguments structure. */
	struct arguments *arguments = state->input;

	switch (key)
	{
		case 'd':
			arguments->debug = 1;
			break;
		case ARGP_KEY_ARG:
			if (state->arg_num >= 2)
				/* Too many arguments. */
				argp_usage (state);
			arguments->args[state->arg_num] = arg;
			break;
		case ARGP_KEY_END:
			if (state->arg_num < 2)
				/* Not enough arguments. */
				argp_usage (state);
			break;
		default:
			return ARGP_ERR_UNKNOWN;
	}
	return 0;
}

static struct argp argp = { options, parse_opt, args_doc, 0 };

int main(int argc, char *argv[])
{
	struct kpatch_elf *kelf;
	struct section *symtab, *sec;
	struct section *ksymsec, *krelasec, *strsec;
	struct arguments arguments;
	unsigned int ksyms_nr, krelas_nr;

	arguments.debug = 0;
	argp_parse (&argp, argc, argv, 0, 0, &arguments);
	if (arguments.debug)
		loglevel = DEBUG;

	elf_version(EV_CURRENT);

	childobj = basename(arguments.args[0]);

	kelf = kpatch_elf_open(arguments.args[0]);

	/*
	 * Sanity checks:
	 * - Make sure all the required sections exist
	 * - Make sure that the number of entries in
	 *   .kpatch.{symbols,relocations} match
	 */
	strsec = find_section_by_name(&kelf->sections, ".kpatch.strings");
	if (!strsec)
		ERROR("missing .kpatch.strings");

	ksymsec = find_section_by_name(&kelf->sections, ".kpatch.symbols");
	if (!ksymsec)
		ERROR("missing .kpatch.symbols section");
	ksyms_nr = (unsigned int)(ksymsec->data->d_size / sizeof(struct kpatch_symbol));

	krelasec = find_section_by_name(&kelf->sections, ".kpatch.relocations");
	if (!krelasec)
		ERROR("missing .kpatch.relocations section");
	krelas_nr = (unsigned int)(krelasec->data->d_size / sizeof(struct kpatch_relocation));

	if (krelas_nr != ksyms_nr)
		ERROR("number of krelas and ksyms do not match");

	/* Create dynrelas from .kpatch.{relocations,symbols} sections */
	create_dynamic_rela_sections(kelf, krelasec, ksymsec, strsec);
	remove_intermediate_sections(kelf);

	kpatch_reindex_elements(kelf);

	symtab = find_section_by_name(&kelf->sections, ".symtab");
	if (!symtab)
		ERROR("missing .symtab section");

	list_for_each_entry(sec, &kelf->sections, list) {
		if (!is_rela_section(sec))
			continue;
		sec->sh.sh_link = symtab->index;
		sec->sh.sh_info = sec->base->index;
		kpatch_rebuild_rela_section_data(sec);
	}

	kpatch_create_shstrtab(kelf);
	kpatch_create_strtab(kelf);
	kpatch_create_symtab(kelf);

	kpatch_write_output_elf(kelf, kelf->elf, arguments.args[1], 0664);
	kpatch_elf_teardown(kelf);
	kpatch_elf_free(kelf);

	return 0;
}
