#include "clld.h"

#include <Bstream.h>

#include <set_error.h>

#include <fstream.h>

//extern "C" {
//	#include <math.h>
//	#include <stdlib.h>
//}

int link_obs ( List<InternalObjectFile> & obs, ObjectFile & resobj ) {
	
	unsigned long aktoff = 0;
	ListItem<InternalObjectFile> * aktob;
	
	for ( aktob = obs.get_head(); aktob; aktob = aktob->get_next()) {
		
		// eventually warn about unused object-files
		if (! aktob->used) {
		    if (verbose)
			cerr << "WARNING: file '"+aktob->name+"' will not be linked\n"
				     "         because none of its exported symbols were used\n";
		} else {
			
			if (! aktob->shared) {
				// apply internal relocation where necessary
				for ( ListItem<InternalReloc> * aktrel = aktob->intrelocs.get_head();
				      aktrel;
				      aktrel = aktrel->get_next()
				    ) {
					
					if (aktrel->obj->shared) {
						ListItem<Import> * imp = aktrel->shimport;
						imp->offset += aktoff;
					} else {
						unsigned long tmp = (unsigned long) aktrel->obj->body.peek(aktrel->offset, 5);
						tmp += aktoff;
						aktrel->obj->body.poke(aktrel->offset, tmp, 5);
					}
				}
				
				// add the actual offset to the relocation table of this object-file
				for ( ListItem<Reloc> * aktr = aktob->relocs.get_head();
				      aktr;
				      aktr = aktr->get_next()
				    ) {
					aktr->offset += aktoff;
				}
				
				if (shared) {
					// add the actual offset to the import-list of the shared library
					
					for ( ListItem<Import> * akti = aktob->shimports.get_head();
					      akti;
					      akti = akti->get_next()
					    ) {
						akti->offset += aktoff;
					}
					
					// add the actual offset to the export-list of this object-file
					
					for ( AVLItem<Symbol> * akte = aktob->exports.get_head();
					      akte;
					      akte = aktob->exports.get_next(akte)
					    ) {
						if (! akte->numerical) {
							akte->ivalue += aktoff;
						}
					}
				}
			
				aktoff += aktob->body.length();
			}
		}
	}

	// now collect the object-files and generate the executable binary / shared lib
	
	for ( aktob = obs.get_head();
	      aktob;
	      aktob = aktob->get_next()
	    ) {
	
		if (aktob->used && !aktob->shared) {
					
			resobj.body += aktob->body;
			
			// add the relocation information for the actual object-file
			for ( ListItem<Reloc> * aktr = aktob->relocs.rem_head();
			      aktr;
			      // see below
			    ) {
			   resobj.relocs.add_tail(aktr);
			   aktr = aktob->relocs.rem_head();
			}
			
			if (shared) {
				// add to the shared-library imports
				for ( ListItem<Import> *akti = aktob->shimports.rem_head();
			         akti;
			         // see below
			        ) {
			   	resobj.imports.add_tail(akti);
			   	akti = aktob->shimports.rem_head();
				}
				
				// add to the shared-library exports
				for ( AVLItem<Symbol> * akts = aktob->exports.get_head();
			         akts;
			         akts = aktob->exports.get_next(akts)
			        ) {
			   	resobj.exports.insert(Symbol(*akts));
				}
				
			}
		}
	}

	return 0;
}

int read_objfile( List<InternalObjectFile> & obs, const Str & name,
		  bool statf, bool library )
{
#ifdef OBSOLETE_LIBGXX
	BIstream f;
	f.open(name, ios::in | ios::binary);
#else
	ifstream f(name, ios::in | ios::binary);
#endif
	if (f.fail()) {
	  if (!library)
		set_error("","unable to open object-file '"+name+"'");
	  return 20;
	}
	
	ListItem<InternalObjectFile> * ni = new ListItem<InternalObjectFile>;
	if (ni == 0) return 20;
	
#ifndef OBSOLETE_LIBGXX
	BIstream in(f);
	in >> *ni;
#else
	f >> *ni;
#endif
	
	if (ni->fail) {
		delete ni;
		add_error("read_objfile() ","cannot read object file '"+name+"'");
		return 20;
	}
	
	if (statf) {
		// we link every object statically
		ni->shared = false;
	}
	
	if (shared) { // if we build a shared library, any object file is "used"
		ni->used = true;
	}
	
	// every reloc inside the single object file will need an additional
	// internal relocation, later:
	for (ListItem<Reloc> * rp = ni->relocs.get_head();
	     rp;
	     rp = rp->get_next()
	    ) {
		ni->intrelocs.add_tail(InternalReloc(ni, rp->offset));
	}
	
	obs.add_tail(ni);
	
	if (verbose) {
		cerr << "read '"+name+"', " << ni->relocs.length() << " relocs, "
		     << ni->imports.length() << " imports, "
		     << ni->exports.size() << " exports.\n";
	}
	
	return 0;
}	  

int next_arg(int & argc, const char ** & aktarg, Str & p) {
	if (argc <= 1) {
		cerr << "missing parameter for option '" << *aktarg << "'\n";
		return -1;
	}
	aktarg++;
	argc--;
	p = Str(*aktarg);
	return 0;
} 

void print_syntax(void) {
	
	cerr << "clld [-v] [-o <target-filename>] [-static <*.slo-file>]\n"
	        "     [-shared] <object-file(s)>... \n";

}

extern const char * version_tag;

bool verbose = false;
bool shared = false;

int main( int argc, const char ** argv ) {
	
	Str		dst_name = "a.out";
	List<Str>	lib_path;
	
	List<InternalObjectFile> obs;
	
	{
		const char ** aktarg = argv;
		if (argc) {
			argc--;
			aktarg++;
		}
		while (argc) {
		
			Str arg(*aktarg);
			
			if (arg.chr(0) != '-') {
				if (read_objfile( obs, arg, false, false )) {
					disp_error();
					return 20;
				}
			} else if (arg.chr(1) == 'L') {
				lib_path.add_tail(arg.after(1));
			
			} else if (arg.chr(1) == 'l') {
			        Str suffix = "/lib" + arg.after(1) + ".slo";
			        ListItem<Str> *lib_dir = lib_path.get_head();

				while(lib_dir) {
				    if (!read_objfile(obs, *lib_dir+suffix,
						      false, true)) {
					break;
				    }
				    lib_dir = lib_dir->get_next();
				}
				if(!lib_dir) {
				    set_error("library not found: ",
					      arg.after(1));
				    disp_error();
				    return 21;
				}
				
			} else if (arg == "-o") {
				if (next_arg (argc, aktarg, dst_name)) return 20;
				
			} else if (arg == "-h" || arg == "-?") {
				print_syntax();
				return 0;
				
			} else if (arg == "-v") {
				verbose = true;
			} else if (arg == "-shared") {
				shared = true;
			} else if (arg == "-static") {
				Str on;
				if (next_arg(argc, aktarg, on)) return 20;
				if (read_objfile( obs, on, true, false )) {
					disp_error();
					return 20;
				}
			} else {
				cerr << "unknown option '" << arg << "', type clld -h for help\n";
				return 20; 
			}
			
			aktarg++;
			argc--;
		}
	
	}
	
	if (verbose) {
		cerr << "clld " << (version_tag+1) << "\nwritten by Lutz Vieweg 1994\n";
	}
	
	if (obs.length() == 0) {
		cerr << "you need to specify at least one object file to link\n";
		return 20;
	}
	
	if (shared) { // test whether all object files are ordinary ones
		for (ListItem<InternalObjectFile> * p = obs.get_head();
		     p;
		     p = p->get_next()
		    ) {
			if (p->shared) {
				cerr << "ERROR: shared libraries have to be built from static\n"
				        "       object-files only, cannot link '" << p->name << "'\n";
				return 20; 
			}
		}
	} else { 
		// we need to link at least the first object file, whether it's used or not
		(obs.get_head())->used = true;
	}
	
	List<CodeImport> cimports;
	List<InternalObjectFileP> shlibs;
	
	if (resolve_references(obs, cimports, shlibs)) {
		cerr << "linkage failed due to unresolved references\n";
		return 20;
	}
	
	ObjectFile resobj; // this will become the resulting object file,
	                   // which parts of it are written depends on
	                   // whether we built an executable or a shared lib.
	                    
	if (link_obs(obs, resobj)) {
		disp_error();
		return 20;
	}
	
	if (shared) {
		// if we are building a shared library, write out the whole object
		// file for (static or dynamic) linking at the host computer
		
		resobj.shared = true;		
		Str shl_name = dst_name+".slo";
		resobj.name = dst_name+".sl";
		
#ifdef OBSOLETE_LIBGXX
		BOstream shl_stream;
		shl_stream.open ( shl_name, ios::out | ios::bin | ios::trunc );
#else
		ofstream shl_stream(shl_name, ios::out | ios::binary | ios::trunc );
#endif
		if (shl_stream.fail()) {
			cerr << "ERROR: unable to open file '"+shl_name+"' for writing\n";
			return 20;
		}
		
#ifndef OBSOLETE_LIBGXX
		BOstream shl(shl_stream);
	
		shl << resobj;
#else
		shl_stream << resobj;
#endif

		if (verbose) {
				cerr << "wrote '"+shl_name+"', " << resobj.relocs.length() << " relocs, "
				     << resobj.imports.length() << " imports, "
				     << resobj.exports.size() << " exports.\n";
		}

#ifdef OBSOLETE_LIBGXX	
		if (shl_stream.fail()) {
#else
		if (shl.fail()) {
#endif
			cerr << "ERROR: write to file '"+shl_name+"' failed\n";
			return 20;
		}
	}
	
	Str bin_name = dst_name;
	if (shared) {
		bin_name += ".sl";
	}
	

	{
		NibStr rdl; // build relocation and dynamic linkage data

		{ // build (non-internal) Reloc-list
			NibStr rlist(5 + resobj.relocs.length()*5 + 5);

			// actual relocation address = 0;
			rlist.poke(0, (unsigned long)0 , 5);
	
			unsigned long offset = 5;
			for ( ListItem<Reloc> * aktrel = resobj.relocs.get_head();
			      aktrel;
			      aktrel = aktrel->get_next()
			    ) {
				rlist.poke(offset, aktrel->offset, 5);
				offset += 5;
			}
			rlist.poke (offset, (unsigned long)0, 5);
			rdl += rlist;
		}

		if (!shared) {
			// add shared library binding informations to executable
			
			{
				unsigned long o;
				// add base-address storage array
				NibStr ba(5 + 5*shlibs.length() + 5);
				for (o = 0; o < shlibs.length()+1; o++) {
					ba.poke(o*5, 0xfffff, 5);
				}
				ba.poke(o*5, (unsigned long)0, 5);
				rdl += ba;
			}
			
			{
				// add code-import-list
				NibStr cilist(cimports.length()*(2+5+5) + 2);
				
				unsigned long offset = 0;
				for ( ListItem<CodeImport> * aktcimp = cimports.get_head();
			 	      aktcimp;
			  	      aktcimp = aktcimp->get_next()
			  	    ) {
					cilist.poke(offset, (aktcimp->targetlib*5)+1, 2);
					offset += 2;
					cilist.poke(offset, aktcimp->liboff, 5);
					offset += 5;
					cilist.poke(offset, aktcimp->offset, 5);
					offset += 5;
				}
				cilist.poke (offset, (unsigned long)0, 2);
				rdl += cilist;
			}
			
			{
				// add the reference list for imports in the shared libraries
				
				NibStr rl;
				
				for ( ListItem<InternalObjectFileP> * aktshlib = shlibs.get_head();
				      aktshlib;
				      aktshlib = aktshlib->get_next()
				    ) {
					
					
					for ( ListItem<Import> * aktimp = aktshlib->iofp->shimports.get_head();
					      aktimp;
					      aktimp = aktimp->get_next()
					    ) {
						rl += NibStr((aktimp->targetlib*5)+1,2);
						rl += NibStr(aktimp->offset,5);
					}
					rl += NibStr((unsigned long)0,2);
				}
				rdl += rl;
			}
			
		} else {
		
			// add import list to the binary of the shared library that
			// is being built
			
			NibStr ilist(resobj.imports.length()*5);

			unsigned long offset = 0;
			for ( ListItem<Import> * akti = resobj.imports.get_tail();
			      akti;
			      akti = akti->get_prev()
			    ) {
				ilist.poke(offset, akti->offset, 5);
				offset += 5;
			}
			rdl += ilist;
		}
	
		// prepend size of relocation-list & shared lib binding data,
		// and add rdl-data to resobj.body
		
		resobj.body = NibStr(rdl.length()+5, 5) + rdl + resobj.body;
	}
	
	// prepend "magic word" for the dynamic linker,
	// which allows it to identify LibraryObjects created
	// by this linker. The last nibble of the "magic word"
	// may be incremented for later revisions of the
	// relocation/shared-library header.
	resobj.body = NibStr((unsigned long)0x47430, 5) + resobj.body;
	
	// capsulate resobj.body in a LibraryData Object (DOEXT0)
	resobj.body = NibStr((unsigned long)0x02b88, 5) +
	              NibStr(resobj.body.length()+5 , 5) +
	              resobj.body;
	
	if (!shared) {
		// append call of dynamic linker (global name "GCCLDD")
		resobj.body += NibStr((unsigned long)0x02e48, 5) +
		               NibStr((unsigned long)0x06 , 2) +
		               NibStr((unsigned long)0x4c434347, 8) +
		               NibStr((unsigned long)0x4444, 4);
	}
	
	{
		// prepend the names of all used shared libraries
		NibStr shn;
		for ( ListItem<InternalObjectFileP> * aktshlib = shlibs.get_tail();
		      aktshlib;
		      aktshlib = aktshlib->get_prev()
		    ) {
			shn += NibStr((unsigned long)0x02e48, 5) +
			       NibStr((unsigned long)(aktshlib->iofp->name.length()), 2);
			      
			for (unsigned long i = 0; i < aktshlib->iofp->name.length(); i++) { 
				shn += NibStr((unsigned long)aktshlib->iofp->name.chr(i), 2);
			}
		}
		resobj.body = shn + resobj.body;
	}
	
	// capsulate resobj.body in DOCOL and SEMI
	resobj.body = NibStr((unsigned long)0x02d9d,5) +
	              resobj.body +
	              NibStr((unsigned long)0x0312b,5);
	
	
	{ // prepend the "HPHP48-E" identifyer
		NibStr ident(16);
		ident.poke (0, (unsigned long)0x50485048 , 8);
		ident.poke (8, (unsigned long)0x452d3834 , 8);
		resobj.body = ident + resobj.body;
	}
	

	// write out the binary (of the executable or shared library)

#ifdef OBSOLETE_LIBGXX
	BOstream dst_stream;
	dst_stream.open (bin_name, ios::out | ios::bin | ios::trunc );
#else
	ofstream dst_stream(bin_name, ios::out | ios::binary | ios::trunc );
#endif
	if (dst_stream.fail()) {
		cerr << "ERROR: unable to open file '"+bin_name+"' for writing\n";
		return 20;
	}
	
#ifndef OBSOLETE_LIBGXX
	BOstream dst(dst_stream);
	
	dst.write( (char *)resobj.body, (resobj.body.length()+1)>>1 );
	if (dst.fail()) {
#else
	dst_stream.write( (char *)resobj.body, (resobj.body.length()+1)>>1 );
	if (dst_stream.fail()) {
#endif
		cerr << "ERROR: write to file '"+bin_name+"' failed\n";
		return 20;
	}
	
	if (verbose) {
		cerr << "wrote '" << bin_name << "', " << resobj.body.length() << " nibbles, "
		     << shlibs.length() << " shared libraries, " << resobj.relocs.length()
		     << " relocs.\n";
	}
	
	return 0;
}




syntax highlighted by Code2HTML, v. 0.9.1