/* 
** Some Portions Copyright 2001, Travis Geiselbrecht. All rights reserved.
** Distributed under the terms of the NewOS License.
*/
/*
** Copyright 1998 Brian J. Swetland
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions, and the following disclaimer.
** 2. 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.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 "../../../headers/private/kernel/boot/bootdir.h"

//#include "sparcbootblock.h"

#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#ifdef sparc
#	define xBIG_ENDIAN 1
#endif
#ifdef i386
#	define xLITTLE_ENDIAN 1
#endif
#if defined(__ppc__) || defined(__PPC__) || defined(__POWERPC__)
#	define xBIG_ENDIAN 1
#endif

#define SWAP32(x) \
	((((x) & 0xff) << 24) | (((x) & 0xff00) << 8) | (((x) & 0xff0000) >> 8) | (((x) & 0xff000000) >> 24))

#if xBIG_ENDIAN
#	define HOST_TO_BENDIAN32(x) (x)
#	define BENDIAN_TO_HOST32(x) (x)
#	define HOST_TO_LENDIAN32(x) SWAP32(x)
#	define LENDIAN_TO_HOST32(x) SWAP32(x)
#endif
#if xLITTLE_ENDIAN
#	define HOST_TO_BENDIAN32(x) SWAP32(x)
#	define BENDIAN_TO_HOST32(x) SWAP32(x)
#	define HOST_TO_LENDIAN32(x) (x)
#	define LENDIAN_TO_HOST32(x) (x)
#endif

#if !xBIG_ENDIAN && !xLITTLE_ENDIAN
#	error not sure which endian the host processor is, please edit bootmaker.c
#endif

// ELF stuff
#define ELF_MAGIC "\x7f""ELF"
#define EI_MAG0	0
#define EI_MAG1 1
#define EI_MAG2 2
#define EI_MAG3 3
#define EI_CLASS 4
#define EI_DATA 5
#define EI_VERSION 6
#define EI_PAD 7
#define EI_NIDENT 16

#define ELFCLASS32 1
#define ELFCLASS64 2
#define ELFDATA2LSB 1
#define ELFDATA2MSB 2

// XXX not safe across all build architectures
typedef unsigned int Elf32_Addr;
typedef unsigned short Elf32_Half;
typedef unsigned int Elf32_Off;
typedef int Elf32_Sword;
typedef unsigned int Elf32_Word;

struct Elf32_Ehdr {
	unsigned char	e_ident[EI_NIDENT];
	Elf32_Half		e_type;
	Elf32_Half		e_machine;
	Elf32_Word		e_version;
	Elf32_Addr		e_entry;
	Elf32_Off		e_phoff;
	Elf32_Off		e_shoff;
	Elf32_Word		e_flags;
	Elf32_Half		e_ehsize;
	Elf32_Half		e_phentsize;
	Elf32_Half		e_phnum;
	Elf32_Half		e_shentsize;
	Elf32_Half		e_shnum;
	Elf32_Half		e_shstrndx;
};

struct Elf32_Phdr {
	Elf32_Word		p_type;
	Elf32_Off		p_offset;
	Elf32_Addr		p_vaddr;
	Elf32_Addr		p_paddr;
	Elf32_Word		p_filesz;
	Elf32_Word		p_memsz;
	Elf32_Word		p_flags;
	Elf32_Word		p_align;
};

#ifndef O_BINARY
#	define O_BINARY 0
#endif

#define LE 0
#define BE 1

// define host endian as default for the target environment
#if xBIG_ENDIAN
static int target_endian = BE;
#else
static int target_endian = LE;
#endif

#define fix(x) ((target_endian == BE) ? HOST_TO_BENDIAN32(x) : HOST_TO_LENDIAN32(x))

static int make_sparcboot = 0;
static int strip_debug = 0;
static char *strip_binary = "strip";


void
die(char *s, char *a)
{
	fprintf(stderr,"error: ");
	fprintf(stderr,s,a);
	fprintf(stderr,"\n");
	exit(1);
}


void *
loadfile(char *file, int *size)
{
	int fd;
	char *data;
	struct stat info;

	if ((fd = open(file, O_BINARY|O_RDONLY)) != -1) {
		if (fstat(fd, &info)) {
			close(fd);
			*size = 0;
			return NULL;
		}
		data = (char *)malloc(info.st_size);
		if (read(fd, data, info.st_size) != info.st_size) {
			close(fd);
			*size = 0;
			return NULL;
		}
		close(fd);
		*size = info.st_size;
		return data;
	}
	*size = 0;
	return NULL;
}


void *
loadstripfile(char *file, int *size)
{
	char temp[256];
	char cmd[4096];
	void *retval;

	if (strip_debug) {
		strcpy(temp, "/tmp/mkboot.XXXXXXXX");
		mktemp(temp);
		sprintf(cmd, "cp %s %s; %s %s", file, temp, strip_binary, temp);
		system(cmd);

		retval = loadfile(temp, size);

		unlink(temp);
	} else {
		retval = loadfile(file, size);
	}

	return retval;
}


// write a boot block to the head of the dir.
// note: the first 0x20 bytes are removed by the sparc prom
// which makes the whole file off by 0x20 bytes
/*
int
writesparcbootblock(int fd, unsigned int blocks)
{
	unsigned char bb[0x200+0x20];

	memset(bb, 0, sizeof(bb));
	memcpy(bb, sparcbootblock, sizeof(sparcbootblock));

	return write(fd, bb, sizeof(bb));
}
*/

typedef struct _nvpair 
{
	struct _nvpair *next;
	char *name;
	char *value;
} nvpair;


typedef struct _section
{
	struct _section *next;
	char *name;
	struct _nvpair *firstnv;
} section;


void
print_sections(section *first)
{
	nvpair *p;

	while (first) {
		printf("\n[%s]\n", first->name);

		for (p = first->firstnv; p; p = p->next) {
			printf("%s=%s\n", p->name, p->value);
        }
		first = first->next;
	}
}


#define stNEWLINE 0
#define stSKIPLINE 1
#define stHEADER 2
#define stLHS 3
#define stRHS 4

section *first = NULL;
section *last = NULL;

section *
load_ini(char *file)
{
	char *data, *end;
	int size;
	int state = stNEWLINE;
	section *cur;
	char *lhs, *rhs;

	if (!(data = loadfile(file, &size)))
		return NULL;

	end = data+size;

	while (data < end) {
		switch (state) {
			case stSKIPLINE:
				if (*data == '\n' || *data == '\r')
					state = stNEWLINE;

				data++;
				break;

			case stNEWLINE:
				if (*data == '\n' || *data == '\r') {
					data++;
					break;
				}

				if (*data == '[') {
					lhs = data+1;
					state = stHEADER;
					data++;
					break;
				}

				if (*data == '#' || *data <= ' ') {
					state = stSKIPLINE;
					data++;
					break;
				}
				lhs = data;
				data++;
				state = stLHS;
				break;
			case stHEADER:
				if (*data == ']') {
					cur = (section *) malloc(sizeof(section));
					cur->name = lhs;
					cur->firstnv = NULL;
					cur->next = NULL;
					if (last) {
						last->next = cur;
						last = cur;
					} else {
						last = first = cur;
					}
					*data = 0;
					state = stSKIPLINE;
				}
				data++;
				break;
			case stLHS:
				if (*data == '\n' || *data == '\r')
					state = stNEWLINE;

				if (*data == '=') {
					*data = 0;
					rhs = data+1;
					state = stRHS;
				}
				data++;
				continue;
			case stRHS:
				if (*data == '\n' || *data == '\r') {
					nvpair *p = (nvpair *)malloc(sizeof(nvpair));
					p->name = lhs;
					p->value = rhs;
					*data = 0;
					p->next = cur->firstnv;
					cur->firstnv = p;
					state = stNEWLINE;
				}
				data++;
				break;
		}
	}
	return first;
}


char *
getval(section *s, char *name)
{
	nvpair *p;
	for (p = s->firstnv; p; p = p->next) {
		if (!strcmp(p->name, name))
			return p->value;
	}
	return NULL;
}


char *
getvaldef(section *s, char *name, char *def)
{
	nvpair *p;
	for (p = s->firstnv; p; p = p->next) {
		if (!strcmp(p->name, name))
			return p->value;
	}
	return def;
}


Elf32_Addr
elf_find_entry(void *buf, int size)
{
	struct Elf32_Ehdr *header;
	struct Elf32_Phdr *pheader;
	char *cbuf = buf;
	int byte_swap;
	int index;

#define SWAPIT(x) ((byte_swap) ? SWAP32(x) : (x))

	if (memcmp(cbuf, ELF_MAGIC, sizeof(ELF_MAGIC)-1) != 0)
		return 0;

	if (cbuf[EI_CLASS] != ELFCLASS32)
		return 0;

	byte_swap = 0;
#if xBIG_ENDIAN		
	if (cbuf[EI_DATA] == ELFDATA2LSB) {
		byte_swap = 1;
	}
#else
	if (cbuf[EI_DATA] == ELFDATA2MSB) {
		byte_swap = 1;
	}
#endif

	header = (struct Elf32_Ehdr *)cbuf;
	pheader = (struct Elf32_Phdr *)&cbuf[SWAPIT(header->e_phoff)];

	// XXX only looking at the first program header. Should be ok
	return SWAPIT(pheader->p_offset);
}
#undef SWAPIT

#define centry bdir.bd_entry[c]
void
makeboot(section *s, char *outfile)
{
	int fd;
	void *rawdata[64];
	int rawsize[64];
	char fill[4096];
	boot_dir bdir;
	int i,c;
	int nextpage = 1; /* page rel offset of next loaded object */

	memset(fill, 0, 4096);
	memset(&bdir, 0, 4096);

	for (i = 0; i < 64; i++) {
		rawdata[i] = NULL;
		rawsize[i] = 0;
	}

	c = 1;

	bdir.bd_entry[0].be_type = fix(BE_TYPE_DIRECTORY);
	bdir.bd_entry[0].be_size = fix(1);
	bdir.bd_entry[0].be_vsize = fix(1);
	rawdata[0] = (void *) &bdir;
	rawsize[0] = 4096;

	strcpy(bdir.bd_entry[0].be_name,"SBBB/Directory");

	while (s) {
		char *type = getvaldef(s, "type", "NONE");
		char *file = getval(s, "file");
		int vsize;
		int size;
		struct stat statbuf;

		if (!type)
			die("section %s has no type", s->name);

		strncpy(centry.be_name,s->name, BOOTDIR_NAMELEN);
		centry.be_name[BOOTDIR_NAMELEN - 1] = 0;

		if (!file)
			die("section %s has no file", s->name);

		rawdata[c] = ((strcmp(type, "elf32")==0) ? loadstripfile : loadfile)(file,&rawsize[c]);
		if (!rawdata[c])
			die("cannot load \"%s\"",file);

		if (stat(file,&statbuf))
			die("cannot stat \"%s\"",file);
		vsize = statbuf.st_size;

		centry.be_size = rawsize[c] / 4096 + (rawsize[c] % 4096 ? 1 : 0);
		centry.be_vsize = (vsize < centry.be_size) ? centry.be_size : vsize;

		centry.be_offset = nextpage;
		nextpage += centry.be_size;

		centry.be_size = fix(centry.be_size);
		centry.be_vsize = fix(centry.be_vsize);
		centry.be_offset = fix(centry.be_offset);

		if (!strcmp(type,"boot")) {
			centry.be_type = fix(BE_TYPE_BOOTSTRAP);
			centry.be_code_vaddr = fix(atoi(getvaldef(s, "vaddr", "0")));
			centry.be_code_ventr = fix(atoi(getvaldef(s, "ventry", "0")));            
		}
		if(!strcmp(type,"code")){
			centry.be_type = fix(BE_TYPE_CODE);
			centry.be_code_vaddr = fix(atoi(getvaldef(s, "vaddr", "0")));
			centry.be_code_ventr = fix(atoi(getvaldef(s, "ventry", "0"))); 
		}
		if (!strcmp(type, "data"))
			centry.be_type = fix(BE_TYPE_DATA);

		if (!strcmp(type, "elf32")) {
			centry.be_type = fix(BE_TYPE_ELF32);
			centry.be_code_vaddr = 0;
			centry.be_code_ventr = fix(elf_find_entry(rawdata[c], rawsize[c]));
		}

		if (centry.be_type == BE_TYPE_NONE)
            die("unrecognized section type \"%s\"", type);

		c++;
		s = s->next;

		if (c == BOOTDIR_MAX_ENTRIES)
			die("too many sections (>63)",NULL);
	}

	if ((fd = open(outfile, O_BINARY|O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0)
		die("cannot write to \"%s\"",outfile);

/* XXX - Hope this isn't needed :(
	if (make_sparcboot)
		writesparcbootblock(fd, nextpage + 1);
*/
	for (i = 0; i < c; i++) {
		write(fd, rawdata[i], rawsize[i]);
		if (rawsize[i] % 4096) 
			write(fd, fill, 4096 - (rawsize[i]%4096));
	}
	close(fd);
}


void
usage(char *name)
{
	char *programName = strrchr(name, '/');
	if (programName == NULL)
		programName = name;
	else
		programName++;

	fprintf(stderr,
		"usage: %s [--littleendian] [--bigendian ] [ --strip-binary <binary ] [ --strip-debug] [ --sparc | -s ] [ <inifile> ... ] -o <bootfile>\n"
		"\tdefaults to "
#if xBIG_ENDIAN
		"\"big-endian\""
#else
		"\"little-endian\""
#endif
		" on this platform\n", programName);
	exit(1);
}


int
main(int argc, char **argv)
{
	char *file = NULL;
	char *programName = argv[0];
	section *s;

	if (argc < 2)
		usage(programName);

	argc--;
	argv++;

	while (argc){
		if (!strcmp(*argv,"--sparc")) {
			make_sparcboot = 1;
		} else if (!strcmp(*argv, "--bigendian")) {
			target_endian = BE;
		} else if (!strcmp(*argv,"-o")) {
			argc--;
			argv++;
			if (argc)
				file = *argv;
			else
				usage(programName);
		} else if (!strcmp(*argv, "--strip-binary")) {
			argc--;
			argv++;
			if (argc)
				strip_binary = *argv;
			else
				usage(programName);
		} else if (!strcmp(*argv, "--strip-debug")) {
			strip_debug = 1;
		} else {
			if (load_ini(*argv) == NULL)
				fprintf(stderr, "warning: cannot load '%s'\n", *argv);
		}
		argc--;
		argv++;
	}

	if ((argc > 3) && !strcmp(argv[3], "-sparc"))
		make_sparcboot = 1;

	if (!file){
		fprintf(stderr,"error: no output specified\n");
		usage(programName);
	}

	if (!first){
		fprintf(stderr, "error: no data to write?!\n");
		usage(programName);
	}

	makeboot(first, file);

	return 0;    
}

