/*
 * patch.c
 *
 * patch together the list of ctors and dtors
 * the order is significant, since we want to
 * initialize libraries first.
 * we assume that symbols are in the same order
 * they were loaded, so we initialize later symbols first.
 */

#include <u.h>
#include <libc.h>
#include <bio.h>
#include <mach.h>

typedef struct Link Link;
typedef void (*Unswab)(ulong, uchar*);

struct Link {
	Link	*next;
	ulong	offset;
};

Link	head;
Link	x;
char	*file;
long	datadel;
int	debug;

void	error(char*);
Link	*findlinks(int, Fhdr*);
Link	*links(Sym*, Link*);
void	patch(int, Link *);
Unswab	nuxi(int, Link);
void	beunswab(ulong, uchar*);
void	leunswab(ulong, uchar*);

void
main(int argc, char *argv[])
{
	Fhdr hdr;
	Link *links;
	int fd;

	argv0 = *argv;
	ARGBEGIN{
	case 'd':
		debug = 1;
		break;
	}ARGEND

	if(argc < 1){
		fprint(2, "usage: patch [-d] file ...\n");
		exits("usage");
	}
	while(argc--){
		file = *argv++;
		fd = open(file, ORDWR);
		if(!crackhdr(fd, &hdr))
			error("can't read header");
		links = findlinks(fd, &hdr);
		patch(fd, links);
		close(fd);
	}
	exits(0);
}

/*
 * find the link and header symbols
 */
Link *
findlinks(int fd, Fhdr *hdr)
{
	Link *link;
	Sym *s;
	int i, nsyms;

	datadel = hdr->datoff - hdr->dataddr;
	nsyms = syminit(fd, hdr);
	if(nsyms <= 0)
		error("can't initialize symbol table");
	s = symbase(&nsyms);
	link = 0;
	head.offset = 0;
	for(i = 0; i < nsyms; i++)
		link = links(s+i, link);
	return link;
}

Link *
links(Sym *s, Link *link)
{
	if(s->type == 'd'){
		if(strcmp("__link", s->name) == 0){
			Link *l;

			l = malloc(sizeof(Link));
			if(l == 0)
				error("out of memory");
			l->next = link;
			l->offset = s->value;
			return l;
		}
		if(strcmp("__head", s->name) == 0)
			head.offset = s->value;
		else if(strcmp("__nuxi", s->name) == 0)
			x.offset = s->value;
	}
	return link;
}

void
patch(int fd, Link *link)
{
	Unswab unswab;
	Link *k;
	uchar next[4];
	ulong crap;

	unswab = nuxi(fd, x);
	memset(next, 0, sizeof next);
	for(k = link; k; k = k->next){
		if(debug){
			seek(fd, k->offset + datadel, 0);
			read(fd, &crap, sizeof crap);
			fprint(2, "link: %lux val %lux\n", k->offset, crap);
		}
		if(seek(fd, k->offset + datadel, 0) < 0)
			error("seek to link failed");
		if(write(fd, next, sizeof(next)) < sizeof(next))
			error("can't write link");
		(*unswab)(k->offset, next);
	}
	if(debug){
		seek(fd, head.offset + datadel, 0);
		read(fd, &crap, sizeof crap);
		fprint(2, "head: %lux val %lux\n", head.offset, crap);
	}
	if(seek(fd, head.offset + datadel, 0) < 0)
		error("seek to head failed\n");
	if(write(fd, next, sizeof(next)) < sizeof(next))
		error("can't write head");
}

Unswab
nuxi(int fd, Link x)
{
	uchar beef[4];

	if(debug)
		fprint(2, "nuxi at %d(%d)\n", x.offset, x.offset + datadel);
	if(seek(fd, x.offset + datadel, 0) < 0)
		error("seek to nuxi failed");
	if(read(fd, beef, sizeof(beef)) < sizeof(beef))
		error("can't read nuxi");
	if(beef[0] == 0xde && beef[1] == 0xad && beef[2] == 0xbe && beef[3] == 0xef)
		return beunswab;
	if(debug)
		fprint(2, "leunswab\n");
	return leunswab;
}

void
beunswab(ulong l, uchar *p)
{
	p[0] = l >> 24;
	p[1] = l >> 16;
	p[2] = l >> 8;
	p[3] = l;
}

void
leunswab(ulong l, uchar *p)
{
	p[0] = l;
	p[1] = l >> 8;
	p[2] = l >> 16;
	p[3] = l >> 24;
}

void
error(char *msg)
{
	fprint(2, "%s: %s for file %s\n", argv0, msg, file);
	exits(msg);
}
