#include <sys/types.h>

#ifdef __linux__
#include "sfsyscall.h"
#endif
#include <stdlib.h>
#include <sys/errno.h>
#include <sys/types.h>
#include <sys/mman.h>


#define NULL	((void *)0)

/*
 * Forget about sparc / alpha / ia64 for now 
 */ 
#define	PAGE_SIZE	4096
#define PAGE_MASK	(PAGE_SIZE-1)
#define round_page(x)           (((x) + PAGE_MASK) & ~PAGE_MASK)

#include "rtld.h"
#include "zlib/zlib.h"

void *
memcpy(void *idst, const void *isrc, size_t n)
{
	char *ret = idst;
	char *dst = idst;
	const char *src = isrc;
	
	while (n--)
		*dst++ = *src++;
	return ret;
}

static int
strncmp(const char *s1, const char *s2, size_t n)
{

	if (n == 0)
		return (0);
	do {
		if (*s1 != *s2++)
			return (*(const unsigned char *)s1 -
				*(const unsigned char *)(s2 - 1));
		if (*s1++ == 0)
			break;
	} while (--n != 0);
	return (0);
}

size_t
strlen(str)
	const char *str;
{
	register const char *s;

	for (s = str; *s; ++s);
	return(s - str);
}

static char *
strstr(const char *s, const char *find)
{
	char c, sc;
	size_t len;

	if ((c = *find++) != 0) {
		len = strlen(find);
		do {
			do {
				if ((sc = *s++) == 0)
					return (NULL);
			} while (sc != c);
		} while (strncmp(s, find, len) != 0);
		s--;
	}
	return ((char *)(unsigned long)s);
}

void *
memset(void *b, int c, size_t len)
{
	char *bb;

	for (bb = (char *)b; len--; )
	  *bb++ = c;
	return (b);
}


int
_sigfillset(set)
	sigset_t *set;
{
	int i;

	for (i = 0; i < _SIG_WORDS; i++)
		set->__bits[i] = ~0U;
	return (0);
}

int
_sigdelset(set, signo)
	sigset_t *set;
	int signo;
{

	if (signo <= 0 || signo > _SIG_MAXSIG) {
		errno = EINVAL;
		return (-1);
	}
	set->__bits[_SIG_WORD(signo)] &= ~_SIG_BIT(signo);
	return (0);
}

/* eof libc functions */

typedef struct handle_s
{
	char *mem_base;   /* base address of maped *.so */
	unsigned long *hash_tab;  /* hash table */
	char *dyn_str_tab;    /* dyn_name table */
	Elf_Sym *dyn_sym_tab; /* dynamic symbol table */
	Elf_Rel *plt_rel;   /* PLT relocation table */
	Elf_Rel *dyn_rel;   /*  relocation table */
} handle_t;


static handle_t libc_handle;
static char *sym;
static unsigned long addr;

#define MLOCK_MAX_SIZE 31744

#ifdef __linux__
#define MAP_FLAGS  MAP_ANONYMOUS
#else
#define MAP_FLAGS  MAP_ANON|MAP_NOCORE
#endif


/*
 * Libraries we need to unpack for server startup
 *
 */
static int const gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */

/* gzip flag byte */
#define ASCII_FLAG   0x01 /* bit 0 set: file probably ascii text */
#define HEAD_CRC     0x02 /* bit 1 set: header CRC present */
#define EXTRA_FIELD  0x04 /* bit 2 set: extra field present */
#define ORIG_NAME    0x08 /* bit 3 set: original file name present */
#define COMMENT      0x10 /* bit 4 set: file comment present */
#define RESERVED     0xE0 /* bits 5..7: reserved */


#define    EOF     (-1)
/* ===========================================================================
      Check the gzip header of a gz_stream opened for reading. Set the stream
    mode to transparent if the gzip magic header is not present; set s->err
    to Z_DATA_ERROR if the magic header is present but the rest of the header
    is incorrect.
    IN assertion: the stream s has already been created sucessfully;
       s->stream.avail_in is zero for the first time, but may be non-zero
       for concatenated .gz files.
*/
static int
check_header(unsigned char **input_buffer, int *input_length)
{
    int method; /* method byte */
    int flags;  /* flags byte */
    int c;
    int len = *input_length;
    unsigned char *inbuf = *input_buffer;

    if (len < 2) 
	    return Z_DATA_ERROR;

    if (inbuf[0] != gz_magic[0] ||
        inbuf[1] != gz_magic[1])
	    return Z_DATA_ERROR;

    len -= 2;
    inbuf += 2;

    /* Check the rest of the gzip header */
    method = inbuf[0];
    flags = inbuf[1];
    if (method != Z_DEFLATED || (flags & RESERVED) != 0)
	    return Z_DATA_ERROR;
    
    /* Discard time, xflags and OS code: */
    inbuf += 8;
    len -= 8;

    if ((flags & EXTRA_FIELD) != 0) { /* skip the extra field */
	    int field_len  =  (uInt)inbuf[0];
	    field_len += ((uInt)inbuf[1])<<8;
	    inbuf += 2;
	    len -= 2;
	    /* len is garbage if EOF but the loop below will quit anyway */
	    while (field_len-- != 0 && *(int *)inbuf != EOF) {
		    inbuf++;
		    len--;
	    }
    }
    /*
     * note that the original name skipping logics seems to be buggy
     *
     */
    if ((flags & ORIG_NAME) != 0) { /* skip the original file name */
	    while ((c = *inbuf) != 0 && c != EOF) {
		    inbuf++;
		    len--;
	    }
	    inbuf++;
	    len--;
    }
    if ((flags & COMMENT) != 0) {   /* skip the .gz file comment */
	    while ((c = *inbuf) != 0 && c != EOF) {
		    inbuf++;
		    len--;
	    }
    }
    if ((flags & HEAD_CRC) != 0) {  /* skip the header crc */
	    inbuf += 2;
	    len -= 2;
    }

    *input_length = len;
    *input_buffer = inbuf;
    return Z_OK;
}


#include "metsrv_main.h"
#include "libcrypto_so.h"
#include "libssl_so.h"
#include "libuc_so.h"

typedef struct library_object {
	void *lo_ptr;
} lobj_t;

typedef struct library_info {
	unsigned int 	l_output_size;
	unsigned int 	l_input_size;
	char 		*l_name;	
	unsigned char	*l_data;
	unsigned char	*l_data_uncompressed;	
	lobj_t		*l_obj;
} linfo_t;

lobj_t metsrv_main_obj;
lobj_t libcrypto_so_obj;
lobj_t libssl_so_obj;
lobj_t libuc_so_obj;

/*
 * The user must make sure that the list of library names matches those
 * in metsrv's symbol table (see elf headers as ldd often gets confused)
 */
static linfo_t startlibs[] = {
	{metsrv_main_size, metsrv_main_length, "meta server", metsrv_main, NULL, &metsrv_main_obj},
	{libcrypto_so_size, libcrypto_so_length, "libcrypto.so", libcrypto_so, NULL, &libcrypto_so_obj},
	{libssl_so_size, libssl_so_length, "libssl.so", libssl_so, NULL, &libssl_so_obj},
	{libuc_so_size, libuc_so_length, "libc.so", libuc_so, NULL, &libuc_so_obj},
	{libuc_so_size, libuc_so_length, "libuc.so", libuc_so, NULL, &libuc_so_obj},
	
	{libuc_so_size, libuc_so_length, "libdl.so", libuc_so, NULL, &libuc_so_obj},
	{libuc_so_size, libuc_so_length, "libz.so",  libuc_so, NULL, &libuc_so_obj},
	{libuc_so_size, libuc_so_length, "libgssapi_krb5.so", libuc_so, NULL, &libuc_so_obj},
	{libuc_so_size, libuc_so_length, "libkrb5.so", libuc_so, NULL, &libuc_so_obj}, 
	{libuc_so_size, libuc_so_length, "libcom_err.so", libuc_so, NULL, &libuc_so_obj},
	{libuc_so_size, libuc_so_length, "libk5crypto.so", libuc_so, NULL, &libuc_so_obj},
	{libuc_so_size, libuc_so_length, "libresolv.so", libuc_so, NULL, &libuc_so_obj},
	{libuc_so_size, libuc_so_length, "libkeyutils.so", libuc_so, NULL, &libuc_so_obj},
	{libuc_so_size, libuc_so_length, "libselinux.so", libuc_so, NULL, &libuc_so_obj},
	{libuc_so_size, libuc_so_length, "libsepol.so", libuc_so, NULL, &libuc_so_obj},
	{0, 0, NULL, NULL, NULL, NULL},
};

static void *
zalloc(void *opaque, unsigned int count, unsigned int size)
{

	return (malloc(count*size));
}

static void
zfree(void *opaque, void *addr)
{

	free(addr);
}

static void *
dumb_malloc(int size)
{

	return mmap (0, size, PROT_WRITE | PROT_READ,
	    MAP_PRIVATE | MAP_FLAGS, -1, 0);
}



typedef void (*func_ptr_type)();
Obj_Entry *entry_start;
func_ptr_type exit_func;

func_ptr_type
_rtld_late(unsigned char *base, unsigned char *buf, ssize_t size,
    func_ptr_type *exit_proc, Obj_Entry **objp);


int
open_object(const char *name, unsigned char **buf, ssize_t *size,
	void *obj)
{
	linfo_t *lib;

	for (lib = startlibs; lib->l_input_size != 0; lib++)	
		if (strstr(name, lib->l_name) != NULL) {
			if (lib->l_obj->lo_ptr == NULL) {
				*buf = lib->l_data_uncompressed;
				*size = lib->l_output_size;
			} else
				*(uintptr_t *)obj = (uintptr_t)lib->l_obj->lo_ptr;
			return (0);
		}
	return (1);
}

void
set_object(const char *name, void *obj)
{
	linfo_t *lib;

	for (lib = startlibs; lib->l_input_size != 0; lib++)	
		if (strstr(name, lib->l_name) != NULL) {
			lib->l_obj->lo_ptr = obj;
			break;
		}
}


#ifdef __i386__
void (*_late_start)(int, char **, ...);
#else	
void (*_late_start)(char **ap, void (*cleanup)(void));
#endif

void call_late_start(void *, int argc, ...);

#if 0
void
call_late_start(int argc, char **argv, char **environ)
{
        __asm__("movl %0, %%edx" : "=rm"(exit_func));
	__asm__("jmp %0", : "=rm"(_late_start));
	printf("calling late start with argv argc=%d argv=%p &argv[0]==%p\n",
	    argc, argv, &argv[0]);
	_late_start(argc, argv[0], argv[1], NULL, environ, NULL);
	
}
#endif
void
metsrv_rtld(int fd, void *base)
{
	z_stream stream;
	int i, status, size;
	linfo_t *lib;
	char *self, *inflate_buffer;
	char *newenviron[] = {"USER=me"};
	char *ap[4];
	char *argv[] = {"metserv_main", (char *)fd, NULL};

	
	printf("fd=%d ap=%p \n", fd, ap);
	ap[0] = (char *)2;
	ap[1] = "metsrv_main";
	ap[2] = (char *) fd;	
	ap[3] = (char *)newenviron;

	memset(&stream, 0, sizeof(stream));
	stream.zalloc = zalloc;
	stream.zfree = zfree;

	for (lib = startlibs; lib->l_input_size != 0; lib++) {
		int input_size  = lib->l_input_size;
		unsigned char *input_buffer = lib->l_data;

		if (check_header(&input_buffer, &input_size) != Z_OK) {
			inflate_buffer = lib->l_data;		
			goto uncompressed;
		}

		/* windowBits is passed < 0 to tell that there is no zlib header.
		 * Note that in this case inflate *requires* an extra "dummy" byte
		 * after the compressed stream in order to complete decompression and
		 * return Z_STREAM_END. Here the gzip CRC32 ensures that 4 bytes are
		 * present after the compressed stream.
		 */
		inflateInit2(&stream, -MAX_WBITS);
		inflate_buffer = dumb_malloc(lib->l_output_size);
		
		stream.avail_in = input_size;
		stream.next_in = input_buffer;
		stream.avail_out = lib->l_output_size;
		stream.next_out = inflate_buffer;
		status = inflate(&stream, Z_FINISH);
		if (status != Z_STREAM_END) {
			/* XXX error */
			exit(1);

		}
	uncompressed:
		lib->l_data_uncompressed = inflate_buffer;
	}

	self = startlibs[0].l_data_uncompressed;
	size = startlibs[0].l_output_size;

	_late_start = (void*) _rtld_late(base, self, size, &exit_func, &entry_start);

#ifdef __i386__
	call_late_start(_late_start, 2, ap[1], ap[2], NULL, NULL);
#else
	_late_start(&ap[0], exit_func);
#endif
}