/* coff.c -- rutines for manipulating coff executable files.  */

#include <coff.h>
#include "tboot.h"

/* Convert COFF to load table.
 * Used to generate loading instructions for use by tboot main().
 * Returns true on successful translation.
 */

int
coff2load(ip, table, data_seg)
struct inode *ip;		/* input: File to read.		*/
struct load_segment table[];	/* output: How to read it.	*/
uint16 *data_seg;		/* output: Where to point es.	*/
{
	FILHDR	fh;	/* COFF file header.		*/
	AOUTHDR oh;	/* COFF optional header.	*/
	SCNHDR	sh;	/* COFF section header.		*/

	int i, j;	/* Loop counters.  */
	fsize_t section_seek;	/* Seek counter for section headers.  */

	/* Read the file header.  */
	iread(ip, &fh, (fsize_t) 0, (uint16) sizeof (fh));


	/* Check for the 5000 possible failures...  */
	/* Is this really a i386 COFF file?  */
	if (I386MAGIC != fh.f_magic) {
		puts("COFF COFF!  File header bad magic.\r\n");
		puts("This is not an i386 COFF file.\r\n");
		return 0;
	}

	/* Is this an executable COFF file?  */
	if (!(fh.f_flags & F_EXEC)) {
		puts("Non-executable COFF file.\r\n");
		return 0;
	}

	/* Does it have the information we need to execute it?  */
	if (sizeof(oh) != fh.f_opthdr) {
		puts("COFF optional header is wrong size.\r\n");
		return 0;
	}

	/*
	 * Pointless witticism:
	 * (Actually, this helped us find a bug in ld.)
	 */
	if (fh.f_timdat < 0x1000) {
		puts(
	 "Wow!  An executable from the first few thousand seconds of time!\r\n"
	 	);
	}

	/* ASSERTION:  We know this to be an executable i386 COFF file.  */

	/* Read the optional header.  */
	/* This is the one with the data we really want.  */
	iread(ip, &oh, (fsize_t) sizeof(fh), (uint16) sizeof (oh));

	/* Validate the optional header.  */
	
	if (NORMAL_MAGIC != oh.magic) {
		puts("COFF COFF!  Optional header bad magic.\r\n");
		puts("This isn't a normal executable file.\r\n");
		return 0;
	}

	/* Read through the section headers,
	 * looking for text and data segments, and
	 * turning them into little toads.  Or something like that.
	 */

#define TEXT table[0]
#define DATA table[1]

	/* Loop until we have both the sections we want, at most twice.  */
	for (TEXT.valid = 0, DATA.valid = 0, j = 0;
	    !(TEXT.valid && DATA.valid) && j < 2;
	    ++j) {

	     for (section_seek = sizeof(FILHDR)+sizeof(AOUTHDR), i = 0;
	     i < fh.f_nscns;	/* i < number of sections  */
	     section_seek += SCNHSZ, ++i) {

		iread(ip, &sh, section_seek, SCNHSZ);
		switch ((int) sh.s_flags) {
		case STYP_TEXT:
			TEXT.valid = 1;
			TEXT.message = "\r\nLoading COHERENT.\r\n";
			TEXT.load_toseg = sys_base;
			TEXT.load_tooffset = 0;
			TEXT.load_offset = sh.s_scnptr;
			TEXT.load_length = sh.s_size;
			break;

		case STYP_DATA:
			/* The text header must already have been read to
			 * put meaningful numbers here.
			 */
			if (TEXT.valid) {
				DATA.valid = 1;
				DATA.message = "\r\nLoading COHERENT data.\r\n";
				/* Round up to next paragraph beyond end
				 * of text.
				 */
				DATA.load_toseg = (sys_base +
					(TEXT.load_length + 15) / 16);
				DATA.load_tooffset = 0;
				DATA.load_offset = sh.s_scnptr;
				DATA.load_length = sh.s_size;
				*data_seg = (sys_base +
					(TEXT.load_length + 15) / 16);
			}
			break;
		case STYP_BSS:
			break;
		default:
			puts("Warning.  Unrecognized COFF section.\r\n");
			break;
		} /* switch (sh.s_flags) */

	     } /* for walk through headers  */

	} /* while haven't got both, at most twice  */
	
	if (!TEXT.valid) {
		puts("Failed to find COFF text section.\r\n");
		return 0;
	}

	if (!DATA.valid) {
		puts("Failed to find COFF data section.\r\n");
		return 0;
	}

	table[2].valid = 0;	/* Terminate the list.  */

	return 1;
}


/*
 * Symbol name.
 */
static char *
symName(sym, str_tab, work)
SYMENT *sym;
char *str_tab, *work;
{
	if (!sym->_n._n_n._n_zeroes)
		return (str_tab + sym->_n._n_n._n_offset - 4);

	memcpy(work, sym->_n._n_name, SYMNMLEN);
	work[SYMNMLEN] = '\0';
	return (work);
}

/*
 * Look up the value of a single data symbol in a coff file,
 * relative to the start of the data segment.
 *
 * We use the symbol "sdata" to find the start of the data segment--
 * this works for 386 COHERENT kernels but will not work in general.
 * It should really fetch the address of the start of the data segment
 * from the data section header.
 */
uint32
wrap_coffnlist(fn, symbol)
char *fn;	/* file name */
char *symbol;	/* symbol to look up */
{
	/* Something goes wrong with looking up symbol if
	 * nlp is automatic rather than static, even with a huge stack.
	 */
	static SYMENT nlp[2];
	uint32 retval;

	/* Start of the data segment.  */
	strcpy(nlp[0]._n._n_name, "sdata");
	nlp[0].n_type = -1;

	nlp[1]._n._n_n._n_zeroes = 0;
	nlp[1]._n._n_n._n_offset = sizeof(int32);
	nlp[1].n_type = -1;

	coffnlist(fn, nlp, symbol, 2);

	retval = ((uint32)nlp[1].n_value) - ((uint32)nlp[0].n_value);

	if (verbose_flag)
		printf("sdata=%lx  %s=%lx  retval=%lx\r\n",
		  nlp[0].n_value, symbol, nlp[1].n_value, retval);

	if (0L == nlp[1].n_value) {
		return(0L);
	} else {
		return retval;
	}
	puts("Unreachable code in wrap_coffnlist().\r\n");
	return(0L);
} /* wrap_coffnlist() */

int
coffnlist(fn, nlp, names, count)
char *fn;	/* file name */
SYMENT *nlp;	/* names to look up */
char *names;	/* long names */
int count;	/* size of passed table */
{
	FILHDR head;
	int fp;
	/* str_tab should be malloc'd. Blows up if file's sym table too big. */
#define STR_TAB_SIZE 5000
	char str_tab[STR_TAB_SIZE];
	long str_length;
	int aux, i;

	if (-1 == (fp = open(fn, 0))) {
		puts("coffnlist open failed\r\n");
		return 0;
	}

	if (FILHSZ != read(fp, &head, FILHSZ) || head.f_magic != I386MAGIC) {
		close (fp);
		printf("coffnlist header read (%d) failed\r\n", FILHSZ);
		return 0;
	}

	lseek(fp, head.f_symptr + (SYMESZ * head.f_nsyms), 0);

	if (sizeof(str_length) != read(fp, &str_length, sizeof(str_length)))
		str_length = 0;
	if (str_length) {
		uint16 len;

		len = str_length -= 4;
		if (len != str_length || len > STR_TAB_SIZE) {
			close (fp);
			printf("coffnlist str table overflow, len = %d\r\n",
			  len);
			return 0;
		}

		if (len != read(fp, str_tab, len)) {
			close (fp);
			puts("coffnlist str read failed\r\n");
			return 0;
		}
	}

	lseek(fp, head.f_symptr, 0);
	for (i = aux = 0; i < head.f_nsyms; i++) {
		SYMENT sym;	/* symbol read in */
		int taux, j;

		if (SYMESZ != read(fp, &sym, SYMESZ)) {
			close (fp);
			puts("coffnlist sym read failed\r\n");
			return 0;
		}

		if (aux) {
			aux--;
			continue;
		}
		aux = sym.n_numaux;
		for (j = taux = 0; j < count; j++) {
			static char n1[SYMNMLEN + 1], n2[SYMNMLEN + 1];
			register SYMENT *np;

			if (taux) {
				taux--;
				continue;
			}
			np = nlp + j;
			taux = np->n_numaux;
			if (np->n_type != -1 ||
			    strcmp(symName(np, names, n1),
			    	   symName(&sym, str_tab, n2)))
				continue;
			np->n_value  = sym.n_value;
			np->n_scnum  = sym.n_scnum;
			np->n_type   = sym.n_type;
			np->n_sclass = sym.n_sclass;
			break;
		}
	}
	close (fp);
	return 1;
}

#ifdef TEST
#include <stdio.h>

main()
{
	int i;
	static SYMENT sym[3];
	static char ptr[]="rootdev";

	strcpy(sym[0]._n._n_name, "NCLIST");
	sym[0].n_type = -1;

	strcpy(sym[1]._n._n_name, "sdata");
	sym[1].n_type = -1;

	sym[2]._n._n_n._n_zeroes = 0;
	sym[2]._n._n_n._n_offset = sizeof(int32);
	sym[2].n_type = -1;

	coffnlist("/at386", sym, ptr, 3);

	for (i = 0; i < 3; ++i) {
		printf("sym[%d]._n._n_name: %s\n", i, sym[i]._n._n_name);
		printf("sym[%d].n_value: %lx\n\n", i, sym[i].n_value);
	}

	printf("\nrootdev as offset: %lx\n", 
		wrap_coffnlist("/at386", "rootdev"));
}
#endif
