/* Add symbols to the kernel from the given file */
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include "oopsd.h"

/* FIXME: configure check --RR */
typedef unsigned long kaddr_t;

struct symbol
{
	unsigned long long addr;
	char *name;
};

struct symbol_table
{
	struct symbol_table *next;
	/* Array terminated by symbol->name = NULL */
	struct symbol *symbols;
};

#include "encode-table.c"

static int addr_cmp(const void *s1, const void *s2)
{
	long long diff;

	diff = ((struct symbol *)s1)->addr - ((struct symbol *)s2)->addr;
	if (diff > 0) return 1;
	else if (diff < 0) return -1;
	else return 0;
}

/* Read the input in format "address name" */
static struct symbol *read_in_symbols(FILE *in)
{
	char name[200];
	unsigned long long addr;
	unsigned int done = 0;
	struct symbol *symbols = NULL;

	while (fscanf(in, "%llx %s", &addr, name) == 2) {
		/* We can't encode strange symbols. */
		if (strspn(name, huff_enc_chars) != strlen(name)) {
			fprintf(stderr, "Ignoring symbol %s\n", name);
			continue;
		}
		symbols = realloc(symbols, (done+2) * sizeof(*symbols));
		symbols[done].addr = addr;
		symbols[done].name = strdup(name);
		done++;
	}

	/* Sort into ascending address order. */
	qsort(symbols, done, sizeof(symbols[0]), addr_cmp);
	/* Mark terminus. */
	symbols[done].name = NULL;
	return symbols;
}

/* Prepend table to linked list, up to but not including the
   num_entries'th entry */
static struct symbol_table *add_table(struct symbol_table *oldstab,
				      struct symbol *symbols,
				      unsigned int num_entries)
{
	struct symbol_table *stab;

	fprintf(stderr, "Starting new table of %u entries at %llx with %s\n",
		num_entries-1, symbols[0].addr, symbols[0].name);
	stab = malloc(sizeof(*stab));
	stab->next = oldstab;
	stab->symbols = malloc(num_entries * sizeof(*symbols));
	memcpy(stab->symbols, symbols, num_entries * sizeof(*symbols));
	stab->symbols[num_entries-1].name = NULL;
	return stab;
}

/* Read in the symbols and split into tables */
static struct symbol_table *read_symbols(FILE *in)
{
	struct symbol *symbols;
	struct symbol_table *stab = NULL;
	unsigned long long last_addr;
	unsigned int last_table, i;

	/* Reads in symbols in ascending address order */
	symbols = read_in_symbols(in);

	last_addr = symbols[0].addr;
	/* How many symbols are within 64k of one another? */
	for (last_table = i = 0; symbols[i].name; i++) {
		if (last_addr + 65536 < symbols[i].addr) {
			stab = add_table(stab, symbols + last_table,
					 i - last_table);
			last_table = i;
		}
		last_addr = symbols[i].addr;
	}
	/* FIXME: Handle case of one function > 64k by making multiple
           tables */
	stab = add_table(stab, symbols + last_table, i - last_table);
	free(symbols);
	return stab;
}

static inline void add_bit(int bit, unsigned char *addr, unsigned int *bitnum)
{
	if (bit) addr[*bitnum / 8] |= (1 << (*bitnum % 8));
	else addr[*bitnum / 8] &= ~(1 << (*bitnum % 8));
	(*bitnum)++;
}

static void encode_char(char c, unsigned char *addr, unsigned int *bitnum)
{
	int code;
	unsigned int i;

	if (c) code = strchr(huff_enc_chars, c) - huff_enc_chars;
	else code = 63;

	for (i = 0; i < huff_encode[code].num; i++)
		add_bit(huff_encode[code].bits[i/8] & (1 << (i%8)),
			addr, bitnum);
}

/* Finally, encode the names */
static unsigned int write_names(struct symbol *symbols, void *bitbuf)
{
	unsigned int i, bitnum;

	bitnum = 0;
	for (i = 0; symbols[i].name; i++) {
		unsigned int j;

		for (j = 0; symbols[i].name[j]; j++)
			encode_char(symbols[i].name[j], bitbuf, &bitnum);
		encode_char(0, bitbuf, &bitnum);
	}

	/* Pad with zeros */
	while (bitnum % 8)
		add_bit(0, bitbuf, &bitnum);

	return bitnum / 8;
}

static struct linux_oopser_symbols *pack_symbols(struct symbol *symbols,
						 const char *prefix,
						 unsigned int *len)
{
	unsigned short num_symbols = 0;
	unsigned int i, totnamelen = 0;
	struct linux_oopser_symbols *packed;

	/* Count symbols and total name length */
	for (i = 0; symbols[i].name; i++) {
		num_symbols++;
		totnamelen += strlen(symbols[i].name)+1;
	}

	/* FIXME: Worst case is, say, 3*original length for names. */
	packed = malloc(sizeof(packed)
			+ sizeof(packed->size[0]) * num_symbols
			+ strlen(prefix) + 1
			+ 3 * totnamelen);
	packed->command = LINUX_OOPSER_SYMBOLS;
	packed->num_syms = num_symbols;
	packed->base_address = symbols[0].addr;

	/* Write in sizes of each symbol */
	for (i = 0; i < num_symbols; i++)
		packed->size[i] = symbols[i+1].addr - symbols[i].addr;

	*len = sizeof(*packed) + num_symbols * sizeof(packed->size[0]);

	/* Copy in prefix name */
	strcpy((void *)packed + *len, prefix);
	*len += strlen(prefix)+1;

	/* Now write out the encoded names */
	*len += write_names(symbols, (void *)packed + *len);

	return packed;
}

#if 0
#include "decode-table.c"

static inline int get_bit(const unsigned char *addr, unsigned int *bitnum)
{
	int bit;

	bit = (addr[*bitnum / 8] & (1 << (*bitnum % 8)));
	(*bitnum)++;
	return !!bit;
}

static char get_char(const unsigned char *addr, unsigned int *bitnum)
{
	int next;
	unsigned int cursor;

	/* Start at zero: jump is an offset from current pos. */
	for (cursor = 0;
	     (next = huff_decode[cursor].jump[get_bit(addr, bitnum)]) > 0;
	     cursor += next) ;
	return huff_dec_chars[-next];
}

/* String routines for parts which can't access sprintf */
/* Poor man's string routines */
static inline char *add_string(char *p, const char *string)
{
	unsigned int i = 0;

	do {
		p[i] = string[i];
	} while (string[i++]);

	return p+i-1;
}

static void decode_name(const char *prefix,
			const unsigned char *encnames,
			unsigned int symnum,
			char *outname)
{
	unsigned int i, bitnum;

	/* Swallow previous names. */
	for (i = 0, bitnum = 0; i < symnum; i++)
		while (get_char(encnames, &bitnum) != 0);

	if (prefix) {
		outname = add_string(outname, prefix);
		outname = add_string(outname, ".");
	}

	do {
		*outname = get_char(encnames, &bitnum);
		outname++;
	} while (outname[-1]);
}

void check_names(struct symbol *symbols, struct linux_oopser_symbols *packed)
{
	unsigned int i;
	unsigned char *encnames;
	char *prefix;
	char outname[256];
	unsigned long pos = symbols[0].addr;

	prefix = (char *)&packed->size[packed->num_syms];
	fprintf(stderr, "Prefix offset is %u\n",
		prefix - (char *)&packed->size[0]);
	encnames = (unsigned char *)(prefix + strlen(prefix)+1);

	for (i = 0; i < packed->num_syms; i++) {
		printf("%llx %s\n", symbols[i].addr, symbols[i].name);
#if 0
		printf("%u: %s size %u\n", i, symbols[i].name, packed->size[i]);
		decode_name(NULL, encnames, i, outname);
		if (strcmp(outname, symbols[i].name) != 0)
			fprintf(stderr, "%s should be %s\n", outname, 
				symbols[i].name);
#endif
		if (pos != symbols[i].addr)
			fprintf(stderr, "%u: 0x%lx should be 0x%llx for %s\n",
				i, pos, symbols[i].addr, symbols[i].name);
		pos += packed->size[i];
	}
	printf("Syms range from %llx to %llx\n",
	       symbols[0].addr, symbols[0].addr + pos);
	printf("Last symbol = %s %llx\n",
	       symbols[packed->num_syms-1].name,
	       symbols[packed->num_syms-1].addr);
}
#endif

/* Add the symbols from the file into kernel: last one is terminator
   (ignored except to give the size of the final function). */
int do_add_symbols(FILE *file, const char *prefix, int oopser_fd)
{
	struct symbol_table *symtabs;

	/* Get array of symbols */
	symtabs = read_symbols(file);

	/* Add each symbol table */
	while (symtabs) {
		/* Write symbols, then encoded names */
		struct linux_oopser_symbols *packed;
		unsigned int len;

		packed = pack_symbols(symtabs->symbols, prefix, &len);
#if 0
		check_names(symtabs->symbols, packed);
#endif
		if (write(oopser_fd, packed, len) != len) {
			perror("Writing symbols to oopser");
			exit(1);
		}
		free(packed);
		symtabs = symtabs->next;
	}
	return 0;
}
