
/*
 * main.c
 *
 * (C) Copyright 1995 Archie L. Cobbs
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "util.h"
#include "error.h"
#include "names.h"
#include "obj.h"
#include "link.h"

/*
 * DEFINITIONS
 */

	OpaqueDecl(FileDesc);
	OpaqueDefn(FileDesc)
	{
		FILE				*fp;
		Names				names;
		char				*filename;
		struct ObjHeader	header;
		unsigned			text_start;
		unsigned			data_start;
		unsigned			bss_start;
		unsigned			treloc_start;
		unsigned			dreloc_start;
		unsigned			off_text;
		unsigned			off_data;
		unsigned			off_treloc;
		unsigned			off_dreloc;
		unsigned			off_symbols;
		unsigned			off_strings;
	};

	#define currArg		(ac ? *av : NULL)
	#define nextArg		((ac && --ac) ? *++av : NULL)

/*
 * GLOBAL VARIABLES
 */

	char				*gProgName = "a2link";
	char				*gUsageMsg = "[-v] [-o outfile] file ...";

	char				*gFileName;
	int					gLineNumber = 0;
	int					gVerbose = FALSE;

	FileDesc			gFiles;
	Names				gNames;
	struct ObjHeader	gHeader;
	Byte				*gSegBuffer[NUM_SEGMENTS];
	struct ObjReloc		*gRelocs[NUM_SEGMENTS];

/*
 * INTERNAL FUNCTIONS
 */

	static	void		LoadSegment(FILE *fp, Byte *buf, int size);
	static	void		LoadSymbols(FileDesc f, int num);
	static	void		LoadRelocs(FileDesc f, struct ObjReloc *reloc,
								int num, unsigned segbase);
	static	void		SetPos(FILE *fp, unsigned posn);

/*
 *	main()
 */

int
main(int ac, char *av[])
{
	int			numfiles, fno;
	char		*outfilename = DEFAULT_OUTFILE;
	FILE		*outfp;

/* Get command line paramters	*/

	while (nextArg && *currArg == '-')
		if (!strcmp(currArg, "-v"))
			gVerbose = TRUE;
		else if (!strcmp(currArg, "-o"))
		{
			if (!nextArg)
				Usage("missing output filename");
			outfilename = currArg;
		}
		else
			Usage(NULL);
	if (ac == 0)
		Usage("no input files specified");

/* Initialize output header */

	memset(&gHeader, 0, sizeof(gHeader));

/* Dynamically allocate file "descriptors" */

	gFiles = GetMem(ac * sizeof(*gFiles));

/* Create a new strings database */

	gNames = NewNames(MAX_SYMBOL_CHARS);

/* Pass One: read sizes and exported symbols */

	if (gVerbose)
		fprintf(stderr, "*** PASS ONE ***\n");

	for (numfiles = 0; currArg; nextArg, numfiles++)
	{
		FileDesc	f = gFiles + numfiles;

		gFileName = f->filename = currArg;

	/* Open file */

		if ((f->fp = fopen(f->filename, "r")) == NULL)
		{
			perror("fopen");
			cerror("can't open file");
		}

	/* Read header */

		if (fread(&f->header, sizeof(struct ObjHeader), 1, f->fp) != 1)
		{
			if (ferror(f->fp))
				perror("fread");
			cerror("can't read header");
		}
		if (f->header.n_magic != OBJ_HEADER_MAGIC)
			cerror("wrong object file format");

	/* Compute offsets */

		(f->off_strings =
			(f->off_symbols =
				(f->off_dreloc =
					(f->off_treloc =
						(f->off_data =
							(f->off_text =
								sizeof(struct ObjHeader))
						+ f->header.n_text)
					+ f->header.n_data)
				+ f->header.n_treloc * sizeof(struct ObjReloc))
			+ f->header.n_dreloc * sizeof(struct ObjReloc))
		+ f->header.n_symbols * sizeof(struct ObjSymbol));

	/* Mark and total */

		f->text_start = gHeader.n_text;
		f->data_start = gHeader.n_data;
		f->bss_start = gHeader.n_bss;
		f->treloc_start = gHeader.n_treloc;
		f->dreloc_start = gHeader.n_dreloc;

		gHeader.n_text += f->header.n_text;
		gHeader.n_data += f->header.n_data;
		gHeader.n_bss += f->header.n_bss;
		gHeader.n_treloc += f->header.n_treloc;
		gHeader.n_dreloc += f->header.n_dreloc;
		gHeader.n_symbols += f->header.n_symbols;

	/* Verbosity */

		if (gVerbose)
		{
			fprintf(stderr, "File \"%s\":\n", f->filename);
			fprintf(stderr, "  TEXT: $%04X - $%04X ($%04X bytes)\n",
				f->text_start, f->text_start + f->header.n_text, f->header.n_text);
			fprintf(stderr, "  DATA: $%04X - $%04X ($%04X bytes)\n",
				f->data_start, f->data_start + f->header.n_data, f->header.n_data);
			fprintf(stderr, "  BSS:  $%04X - $%04X ($%04X bytes)\n",
				f->bss_start, f->bss_start + f->header.n_bss, f->header.n_bss);
		}

	/* Load strings table */

		SetPos(f->fp, f->off_strings);
		f->names = NamesLoad(f->fp, f->header.n_strings);

	/* Load exported symbols */

		SetPos(f->fp, f->off_symbols);
		LoadSymbols(f, f->header.n_symbols);
	}

/* Abort if any errors */

	if (gNumErrors)
		cerror("cannot complete linking");

/* Allocate segment and relocation buffers */

	gSegBuffer[CodeSegment] = GetMem(gHeader.n_text);
	gSegBuffer[DataSegment] = GetMem(gHeader.n_data);

	gRelocs[CodeSegment] = GetMem(gHeader.n_treloc * sizeof(struct ObjReloc));
	gRelocs[DataSegment] = GetMem(gHeader.n_dreloc * sizeof(struct ObjReloc));

/* Pass Two: join everything */

	if (gVerbose)
		fprintf(stderr, "*** PASS TWO ***\n");

	for (fno = 0; fno < numfiles; fno++)
	{
		FileDesc	f = gFiles + fno;

		gFileName = f->filename;

	/* Read in text segment */

		SetPos(f->fp, f->off_text);
		LoadSegment(f->fp, gSegBuffer[CodeSegment]
					+ f->text_start, f->header.n_text);

	/* Read in data segment */

		SetPos(f->fp, f->off_data);
		LoadSegment(f->fp, gSegBuffer[DataSegment]
					+ f->data_start, f->header.n_data);

	/* Read in text relocations */

		SetPos(f->fp, f->off_treloc);
		LoadRelocs(f, gRelocs[CodeSegment] + f->treloc_start,
			f->header.n_treloc, f->text_start);

	/* Read in data relocations */

		SetPos(f->fp, f->off_dreloc);
		LoadRelocs(f, gRelocs[DataSegment] + f->dreloc_start,
			f->header.n_dreloc, f->data_start);

	/* Close file */

		fclose(f->fp);
	}

/* Abort if any errors */

	if (gNumErrors)
		cerror("errors prevent successful link");

/* Open output file */

	if ((outfp = fopen(gFileName = outfilename, "w")) == NULL)
	{
		perror("fopen");
		cerror("can't open file for writing");
	}

/* Write output */

	WriteObj(outfp, &gHeader);

/* Done */

	fclose(outfp);
	return(0);
}

/*
 * LoadSegment()
 */

static	void
LoadSegment(FILE *fp, Byte *buf, int size)
{
	if (fread(buf, 1, size, fp) != size)
	{
		if (ferror(fp))
			perror("fread");
		cerror("can't read segment");
	}
}

/*
 * LoadSymbols()
 */

static	void
LoadSymbols(FileDesc f, int num)
{
	int					k;
	struct ObjSymbol	symbol;
	Symbol				sym;
	unsigned			nameid;

	for (k = 0; k < num; k++)
	{

	/* Read next symbol */

		if (fread(&symbol, sizeof(symbol), 1, f->fp) != 1)
		{
			if (ferror(f->fp))
				perror("fread");
			cerror("can't read symbol");
		}

	/* Does it already exist? */

		if ((sym = GetSym(nameid = NameID(gNames,
			Name(f->names, symbol.s_name)))) != NULL)
		{
			uerror("\"%s\": multiply defined (file \"%s\")",
				Name(gNames, nameid), sym->filename);
			continue;
		}

	/* Add it to table */

		sym = DefineSym(nameid);
		sym->filename = f->filename;

		switch ((sym->segment = symbol.s_segment))
		{
			case CodeSegment:
				sym->offset = f->text_start;
				break;
			case DataSegment:
				sym->offset = f->data_start;
				break;
			case BssSegment:
				sym->offset = f->bss_start;
				break;
		}
		sym->offset += symbol.s_offset;
	}
}

/*
 * LoadRelocs()
 */

static	void
LoadRelocs(FileDesc f, struct ObjReloc *reloc, int num, unsigned segbase)
{
	int		k;

	for (k = 0; k < num; k++, reloc++)
	{

	/* Read in reloc */

		if (fread(reloc, sizeof(*reloc), 1, f->fp) != 1)
		{
			if (ferror(f->fp))
				perror("fread");
			cerror("can't read reloc");
		}

	/* Adjust address in segment to which the patch applies */

		reloc->r_address += segbase;

	/* Adjust value to be patched in. For segment relative relocations,     */
	/* this means changing the offset from the base of the target segment.  */
	/* For symbol relative relocations, this means updating the hash value. */

		if (reloc->r_segrel)
		{
			unsigned	targetBase = 0;

			switch (reloc->r_name)
			{
				case CodeSegment:
					targetBase = f->text_start;
					break;
				case DataSegment:
					targetBase = f->data_start;
					break;
				case BssSegment:
					targetBase = f->bss_start;
					break;
				default:
					cerror("bad reloc target segment %d in \"%s\"",
						reloc->r_name, f->filename);
			}
			reloc->r_offset += targetBase;
		}
		else
			reloc->r_name = NameID(gNames, Name(f->names, reloc->r_name));
	}
}

/*
 * SetPos()
 */

static	void
SetPos(FILE *fp, unsigned posn)
{
	if (fseek(fp, posn, SEEK_SET) < 0)
	{
		perror("fseek");
		cerror("can't seek");
	}
}

