/*
 *	UNIX dynamic loader
 *
 * Currently only supports stuff using the dl* API.
 *
 * Copyright 1998 Marcus Meissner
 *
 * FIXME: 	Small reentrancy problem.
 * IDEA(s):	could be used to split up shell32,comctl32...
 */

#include "config.h"
#include "wine/port.h"

#include <assert.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif

#include "snoop.h"
#include "wine/file.h"
#include "module.h"
#include "wine/debug.h"
#include "winerror.h"

WINE_DEFAULT_DEBUG_CHANNEL(win32);

typedef struct {
	WORD	popl	WINE_PACKED;	/* 0x8f 0x05 */
	DWORD	addr_popped WINE_PACKED;/* ...  */
	BYTE	pushl1	WINE_PACKED;	/* 0x68 */
	DWORD	newret WINE_PACKED;	/* ...  */
	BYTE	pushl2 	WINE_PACKED;	/* 0x68 */
	DWORD	origfun WINE_PACKED;	/* original function */
	BYTE	ret1	WINE_PACKED;	/* 0xc3 */
	WORD	addesp 	WINE_PACKED;	/* 0x83 0xc4 */
	BYTE	nrofargs WINE_PACKED;	/* nr of arguments to add esp, */
	BYTE	pushl3	WINE_PACKED;	/* 0x68 */
	DWORD	oldret	WINE_PACKED;	/* Filled out from popl above  */
	BYTE	ret2	WINE_PACKED;	/* 0xc3 */
} ELF_STDCALL_STUB;

#define UNIX_DLL_ENDING		"so"

#define	STUBSIZE		4095
#define STUBOFFSET  (sizeof(IMAGE_DOS_HEADER) + \
                     sizeof(IMAGE_NT_HEADERS) + \
                     sizeof(IMAGE_SECTION_HEADER))

static FARPROC ELF_FindExportedFunction( WINE_MODREF *wm, LPCSTR funcName, BOOL snoop );

static HMODULE ELF_CreateDummyModule( LPCSTR libname, LPCSTR modname )
{
	PIMAGE_DOS_HEADER	dh;
	PIMAGE_NT_HEADERS	nth;
	PIMAGE_SECTION_HEADER	sh;
	HMODULE hmod;

	hmod = (HMODULE)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY,
                                     sizeof(IMAGE_DOS_HEADER) +
                                     sizeof(IMAGE_NT_HEADERS) +
                                     sizeof(IMAGE_SECTION_HEADER) + STUBSIZE );
	dh = (PIMAGE_DOS_HEADER)hmod;
	dh->e_magic = IMAGE_DOS_SIGNATURE;
	dh->e_lfanew = sizeof(IMAGE_DOS_HEADER);
	nth = PE_HEADER(hmod);
	nth->Signature = IMAGE_NT_SIGNATURE;
	nth->FileHeader.Machine = IMAGE_FILE_MACHINE_I386;
	nth->FileHeader.NumberOfSections = 1;
	nth->FileHeader.SizeOfOptionalHeader = sizeof(IMAGE_OPTIONAL_HEADER);
	nth->FileHeader.Characteristics =
		IMAGE_FILE_RELOCS_STRIPPED|IMAGE_FILE_LINE_NUMS_STRIPPED|
		IMAGE_FILE_LOCAL_SYMS_STRIPPED|IMAGE_FILE_32BIT_MACHINE|
		IMAGE_FILE_DLL|IMAGE_FILE_DEBUG_STRIPPED;
	nth->OptionalHeader.Magic = IMAGE_NT_OPTIONAL_HDR_MAGIC;
	nth->OptionalHeader.SizeOfCode = 0;
	nth->OptionalHeader.SizeOfInitializedData = 0;
	nth->OptionalHeader.SizeOfUninitializedData = 0;
	nth->OptionalHeader.AddressOfEntryPoint	= 0;
	nth->OptionalHeader.BaseOfCode		= 0;
	nth->OptionalHeader.MajorOperatingSystemVersion = 4;
	nth->OptionalHeader.MajorImageVersion	= 4;
	nth->OptionalHeader.SizeOfImage		= 0;
	nth->OptionalHeader.SizeOfHeaders	= 0;
	nth->OptionalHeader.Subsystem		= IMAGE_SUBSYSTEM_NATIVE;
	nth->OptionalHeader.DllCharacteristics	= 0;
	nth->OptionalHeader.NumberOfRvaAndSizes	= 0;

	/* allocate one code section that crosses the whole process range
	 * (we could find out from internal tables ... hmm )
	 */
	sh=(PIMAGE_SECTION_HEADER)(nth+1);
	strcpy(sh->Name,".text");
	sh->Misc.VirtualSize	= STUBSIZE;
	sh->VirtualAddress	= STUBOFFSET; /* so snoop can use it ... */
	sh->SizeOfRawData	= STUBSIZE;
	sh->PointerToRawData	= 0;
	sh->Characteristics	= IMAGE_SCN_CNT_CODE|IMAGE_SCN_CNT_INITIALIZED_DATA|IMAGE_SCN_MEM_EXECUTE|IMAGE_SCN_MEM_READ;
        return hmod;
}

WINE_MODREF *ELF_LoadLibraryExA( LPCSTR libname, DWORD flags)
{
	WINE_MODREF	*wm;
        HMODULE		hmod;
	char		*modname,*s,*t,*x;
	LPVOID		*dlhandle;
	char		error[256];

	t = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY,
                       strlen(libname) + strlen("lib.so") + 1 );
	*t = '\0';
	/* copy path to tempvar ... */
	s=strrchr(libname,'/');
	if (!s)
		s=strrchr(libname,'\\');
	if (s) {
		s++; /* skip / or \ */
		/* copy everything up to s-1 */
		memcpy(t,libname,s-libname);
		t[s-libname]= '\0';
	} else
		s = (LPSTR)libname;
	modname = s;
	/* append "lib" foo ".so" */
	strcat(t,"lib");
	x = t+strlen(t);
	strcat(t,s);
	s = strchr(x,'.');
	if (s) {
            while (s) {
		if (!FILE_strcasecmp(s,".dll")) {
                    strcpy(s+1,UNIX_DLL_ENDING);
                    break;
		}
		s=strchr(s+1,'.');
            }
	} else {
	    	strcat(x,"."UNIX_DLL_ENDING);
	}

        /* grab just the last piece of the path/filename
           which should be the name of the library we are
           looking to load. increment by 1 to skip the DOS slash */
        s = strrchr(t,'\\');
        s++;

       /* ... and open the library pointed by s, while t points
         points to the ENTIRE DOS filename of the library
         t is returned by HeapAlloc() above and so is also used
         with HeapFree() below */
	dlhandle = wine_dlopen(s,RTLD_NOW,error,sizeof(error));
	if (!dlhandle) {
                WARN("failed to load %s: %s\n", s, error);
		HeapFree( GetProcessHeap(), 0, t );
		SetLastError( ERROR_FILE_NOT_FOUND );
		return NULL;
	}

	hmod = ELF_CreateDummyModule( t, modname );

	SNOOP_RegisterDLL(hmod,libname,0,STUBSIZE/sizeof(ELF_STDCALL_STUB));

        wm = PE_CreateModule( hmod, libname, 0, 0, FALSE );
        wm->find_export = ELF_FindExportedFunction;
	wm->dlhandle = dlhandle;
	return wm;
}

static FARPROC ELF_FindExportedFunction( WINE_MODREF *wm, LPCSTR funcName, BOOL snoop )
{
	LPVOID			fun;
	int			i,nrofargs = 0;
	ELF_STDCALL_STUB	*stub, *first_stub;
	char			error[256];

	if (!HIWORD(funcName)) {
		ERR("Can't import from UNIX dynamic libs by ordinal, sorry.\n");
		return (FARPROC)0;
	}
	fun = wine_dlsym(wm->dlhandle,funcName,error,sizeof(error));
        if (!fun)
        {
            /* we sometimes have an excess '_' at the beginning of the name */
            if (funcName[0]=='_')
            {
		funcName++ ;
		fun = wine_dlsym(wm->dlhandle,funcName,error,sizeof(error));
            }
        }
	if (!fun) {
		/* Function@nrofargs usually marks a stdcall function
		 * with nrofargs bytes that are popped at the end
		 */
            LPCSTR t;
            if ((t = strchr(funcName,'@')))
            {
                LPSTR fn = HeapAlloc( GetProcessHeap(), 0, t - funcName + 1 );
                memcpy( fn, funcName, t - funcName );
                fn[t - funcName] = 0;
                nrofargs = 0;
                sscanf(t+1,"%d",&nrofargs);
                fun = wine_dlsym(wm->dlhandle,fn,error,sizeof(error));
                HeapFree( GetProcessHeap(), 0, fn );
            }
	}
	/* We sometimes have Win32 dlls implemented using stdcall but UNIX
	 * dlls using cdecl. If we find out the number of args the function
	 * uses, we remove them from the stack using two small stubs.
	 */
        stub = first_stub = (ELF_STDCALL_STUB *)((char *)wm->module + STUBOFFSET);
	for (i=0;i<STUBSIZE/sizeof(ELF_STDCALL_STUB);i++) {
		if (!stub->origfun)
			break;
		if (stub->origfun == (DWORD)fun)
			break;
		stub++;
	}
	if (i==STUBSIZE/sizeof(ELF_STDCALL_STUB)) {
		ERR("please report, that there are not enough slots for stdcall stubs in the ELF loader.\n");
		assert(i<STUBSIZE/sizeof(ELF_STDCALL_STUB));
	}
	if (!stub->origfun)
		stub->origfun=(DWORD)fun; /* just a marker */

	if (fun && nrofargs) { /* we don't need it for 0 args */
		/* Selfmodifying entry/return stub for stdcall -> cdecl
		 * conversion.
		 *  - Pop returnaddress directly into our return code
		 * 		popl <into code below>
		 *  - Replace it by pointer to start of our returncode
		 * 		push $newret
		 *  - And call the original function
		 * 		jmp $orgfun
		 *  - Remove the arguments no longer needed
		 * newret: 	add esp, <nrofargs>
		 *  - Push the original returnvalue on the stack
		 *		pushl <poppedvalue>
		 *  - And return to it.
		 *		ret
		 */

		/* FIXME: The function stub is not reentrant. */

		((LPBYTE)&(stub->popl))[0]	  = 0x8f;
		((LPBYTE)&(stub->popl))[1]	  = 0x05;
		stub->addr_popped = (DWORD)&(stub->oldret);
		stub->pushl1	  = 0x68;
		stub->newret	  = (DWORD)&(stub->addesp);
		stub->pushl2	  = 0x68;
		stub->origfun	  = (DWORD)fun;
		stub->ret1	  = 0xc3;
		((LPBYTE)&(stub->addesp))[0]=0x83;
		((LPBYTE)&(stub->addesp))[1]=0xc4;
		stub->nrofargs	  = nrofargs;
		stub->pushl3	  = 0x68;
			/* filled out by entrycode */
		stub->oldret	  = 0xdeadbeef;
		stub->ret2	  = 0xc3;
		fun=(FARPROC)stub;
	}
	if (!fun) {
		FIXME("function %s not found: %s\n",funcName,error);
		return fun;
	}
	fun = SNOOP_GetProcAddress(wm->module,funcName,stub-first_stub,fun);
	return (FARPROC)fun;
}
