/* ``The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved via the world wide web at http://www.erlang.org/.
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Initial Developer of the Original Code is Ericsson Utvecklings AB.
 * Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
 * AB. All Rights Reserved.''
 * 
 *     $Id$
 */

/* 
 * Interface functions to the dynamic linker using dl* functions.
 * (As far as I know it works on SunOS 4, 5, Linux and FreeBSD. /Seb) 
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif
#include <vxWorks.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <a_out.h>
#include <symLib.h>
#include <loadLib.h>
#include <unldLib.h>
#include <moduleLib.h>
#include <sysSymTbl.h>
#include "sys.h"
#include "global.h"
#include "erl_alloc.h"
#include "erl_driver.h"

#define EXT_LEN          4
#define FILE_EXT         ".eld"
#define ALT_FILE_EXT     ".o"
#define DRIVER_INIT_SUFFIX "_init"

static MODULE_ID get_mid(char *);
static FUNCPTR lookup(char *);

typedef enum {
    NoError,
    ModuleNotFound,
    ModuleNotUnloadable,
    UnknownError
} FakeSytemError;

static char *errcode_tab[] = {
    "No error",
    "Module/file not found",
    "Module cannot be unloaded",
    "Unknown error"
};

void erl_sys_ddll_init(void) {
    return;
}
/* 
 * Open a shared object
 */
int erts_sys_ddll_open(char *full_name, void **handle)
{
    MODULE_ID mid;
    int len;
    int ret = ERL_DE_NO_ERROR;
    static char dlname[PATH_MAX + EXT_LEN + 1];

    if ((mid = get_mid(full_name)) == NULL) {
	if ((len = sys_strlen(full_name)) > PATH_MAX-EXT_LEN-1) {
	    ret = ERL_DE_LOAD_ERROR_NAME_TO_LONG;
	    goto done;
	}
	sys_strcpy(dlname, full_name);
	sys_strcpy(dlname+len, FILE_EXT);
	if((mid = get_mid(dlname)) == NULL) {
	    sys_strcpy(dlname+len, ALT_FILE_EXT);
	    if((mid = get_mid(dlname)) == NULL) {
		ret = ERL_DE_DYNAMIC_ERROR_OFFSET - ((int) ModuleNotFound);
		goto done;
	    }
	}
    }
    *handle = (void *) mid;
 done:
    return ret;
}

/* 
 * Find a symbol in the shared object
 */
#define PREALLOC_BUFFER_SIZE 256
int erts_sys_ddll_sym(void *handle, char *func_name, void **function)
{
    FUNCPTR proc;
    static char statbuf[PREALLOC_BUFFER_SIZE];
    char *buf = statbuf;
    int need;

    if ((proc = lookup(func_name)) == NULL) {
	if ((need = strlen(func_name)+2) > PREALLOC_BUFFER_SIZE) {
	    buf = erts_alloc(ERTS_ALC_T_DDLL_TMP_BUF,need);
	}
	buf[0] = '_';
	sys_strcpy(buf+1,func_name);
	proc = lookup(buf);
	if (buf != statbuf) {
	    erts_free(ERTS_ALC_T_DDLL_TMP_BUF, buf);
	}
	if (proc == NULL) {
	    return  ERL_DE_LOOKUP_ERROR_NOT_FOUND;
	}
    }
    *function = (void *) proc;
    return ERL_DE_NO_ERROR;
}

/* XXX:PaN These two will be changed with new driver interface! */

/* 
 * Load the driver init function, might appear under different names depending on object arch... 
 */

int erts_sys_ddll_load_driver_init(void *handle, void **function)
{
    MODULE_ID mid = (MODULE_ID) handle;
    char *modname;
    char *cp;
    static char statbuf[PREALLOC_BUFFER_SIZE];
    char *fname = statbuf;
    int len;
    int res;
    void *func;
    int need;

    if((modname = moduleNameGet(mid)) == NULL) {
	return ERL_DE_DYNAMIC_ERROR_OFFSET - ((int) ModuleNotFound);
    }
    
    if((cp = strrchr(modname, '.')) == NULL) {
	len = strlen(modname);
    } else {
	len = cp - modname;
    }
    
    need =  len + strlen(DRIVER_INIT_SUFFIX) + 1;
    if (need > PREALLOC_BUFFER_SIZE) {
	fname = erts_alloc(ERTS_ALC_T_DDLL_TMP_BUF, need); /* erts_alloc exits on failure */
    }
    sys_strncpy(fname, modname, len);
    fname[len] = '\0';
    sys_strcat(fname, DRIVER_INIT_SUFFIX);
    res = erts_sys_ddll_sym(handle, fname, &func);
    if (fname != statbuf) {
	erts_free(ERTS_ALC_T_DDLL_TMP_BUF, fname);
    }
    if ( res != ERL_DE_NO_ERROR) {
	return res;
    }
    *function = func;
    return ERL_DE_NO_ERROR;
}

/* 
 * Call the driver_init function, whatever it's really called, simple on unix... 
*/
void *erts_sys_ddll_call_init(void *function) {
    void *(*initfn)(void) = function;
    return (*initfn)();
}


/* 
 * Close a chared object
 */
int erts_sys_ddll_close(void *handle)
{
    MODULE_ID mid = (MODULE_ID) handle;
    if (unld(mid, 0) < 0) {
	return  ERL_DE_DYNAMIC_ERROR_OFFSET - ((int) ModuleNotUnloadable);
    }
    return  ERL_DE_NO_ERROR;
}

/*
 * Return string that describes the (current) error
 */
char *erts_sys_ddll_error(int code)
{
    int actual_code;
    if (code > ERL_DE_DYNAMIC_ERROR_OFFSET) {
	return "Unspecified error";
    }
    actual_code = -1*(code - ERL_DE_DYNAMIC_ERROR_OFFSET);
    if (actual_code > ((int) UnknownError)) {
	actual_code = UnknownError;
    }
    return  errcode_tab[actual_code];
}

static FUNCPTR lookup(char *sym)
{
    FUNCPTR entry;
    SYM_TYPE type;
    
    if (symFindByNameAndType(sysSymTbl, sym, (char **)&entry,
			     &type, N_EXT | N_TEXT, N_EXT | N_TEXT) != OK) {
	return NULL ;
    }
    return entry;
}

static MODULE_ID get_mid(char* name) 
{ 
    int fd;
    MODULE_ID mid = NULL;
    
    if((fd = open(name, O_RDONLY, 0664)) >= 0) {
	mid = loadModule(fd, GLOBAL_SYMBOLS);
	close(fd);
    }
    return mid;
}
