#include <config.h>

#if HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include <stdio.h>
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <ctype.h>
#if HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#include <dlfcn.h>


#include "mibincl.h"
#include "struct.h"
#include "read_config.h"
#include "agent_read_config.h"

#include "dlmod.h"

static struct dlmod *dlmods = NULL;

int dlmod_next_index = 1;

static void dlmod_parse_config(char *, char *);
static void dlmod_free_config (void);
static char dlmod_path[1024];

void 
init_dlmod (void) 
{
    char *p;
    int l;

    snmpd_register_config_handler("dlmod", dlmod_parse_config,
				  dlmod_free_config,
				  "dlmod-file-to-load");

    p = getenv("SNMP_DLMOD_PATH");
    strncpy(dlmod_path, DLMOD_DEFAULT_PATH, sizeof(dlmod_path));
    if (p) {
	if (p[0] == '+') {
	    l = strlen(dlmod_path);
	    if (dlmod_path[l - 1] != ':') 
		strncat(dlmod_path, ":", sizeof(dlmod_path) - l);
	    strncat(dlmod_path, p + 1, 
		    sizeof(dlmod_path) - strlen(dlmod_path));
	} else 
	    strncpy(dlmod_path, p, sizeof(dlmod_path));
    }
#if 1
    DEBUGMSGTL(("dlmod", "dlmod_path: %s\n", dlmod_path));
#endif
}

void 
deinit_dlmod (void)
{
    snmpd_unregister_config_handler("dlmod");
}

static void 
dlmod_parse_config(char *token,
		   char *cptr)
{
    char *dlm_name, *dlm_path;
    struct dlmod *dlm;
  
    if (cptr == NULL) {
	config_perror("Bad dlmod line");
	return;
    }
    /* remove comments */
    *(cptr + strcspn(cptr, "#;\r\n")) = '\0';

    dlm = dlmod_create_module();
    if (!dlm)
	return;

    /* dynamic module name */
    dlm_name = strtok(cptr, "\t "); 
    if (dlm_name == NULL) {
	config_perror("Bad dlmod line");
	dlmod_delete_module(dlm);
	return;
    }
    strncpy(dlm->name, dlm_name, sizeof(dlm->name));

    /* dynamic module path */
    dlm_path = strtok(NULL, "\t ");
    if (dlm_path) 
	strncpy(dlm->path, dlm_path, sizeof(dlm->path));
    else
	strncpy(dlm->path, dlm_name, sizeof(dlm->path));

    dlmod_load_module(dlm);
	
    if (dlm->status == DLMOD_ERROR) 
 snmp_log(LOG_ERR, "%s\n", dlm->error);
		
}

static void 
dlmod_free_config (void) 
{
    struct dlmod *dtmp, *dtmp2;
  
    for (dtmp = dlmods; dtmp != NULL;) {
	dtmp2 = dtmp;
	dtmp = dtmp->next;
	dlmod_unload_module(dtmp2);
	free(dtmp2);
    }
    dlmods = NULL;
    dlmod_next_index = 1;
}

struct dlmod *
dlmod_create_module (void) 
{
    struct dlmod **pdlmod, *dlm;
#if 1
    DEBUGMSGTL(("dlmod", "dlmod_create_module\n"));
#endif
    dlm = calloc(1, sizeof(struct dlmod));
    if (dlm == NULL) 
	return NULL;

    dlm->index = dlmod_next_index++;
    dlm->status = DLMOD_UNLOADED;

    for (pdlmod = &dlmods; *pdlmod != NULL; pdlmod = &((*pdlmod)->next)) 
	;
    (*pdlmod) = dlm;

    return dlm;
}

void
dlmod_delete_module(struct dlmod *dlm)
{
    struct dlmod **pdlmod;
 
#if 1
    DEBUGMSGTL(("dlmod", "dlmod_delete_module\n"));
#endif
    if (!dlm || dlm->status != DLMOD_UNLOADED)
	return;

    for (pdlmod = &dlmods; *pdlmod; pdlmod = &((*pdlmod)->next)) 
	if (*pdlmod == dlm) {
	    *pdlmod = dlm->next;
	    free(dlm);
	    return;
	}
}

void
dlmod_load_module(struct dlmod *dlm)
{
    char sym_init[64];
    char *p, tmp_path[255];
    int (*dl_init)(void);
#if 1
    DEBUGMSGTL(("dlmod", "dlmod_load_module\n"));
#endif
  
    if (!dlm || !dlm->path || !dlm->name || dlm->status != DLMOD_UNLOADED)
	return;


    if (dlm->path[0] == '/') { 
	dlm->handle = dlopen(dlm->path, RTLD_NOW);
	if (dlm->handle == NULL) {
	    snprintf(dlm->error, sizeof(dlm->error), 
		     "dlopen failed: %s", dlerror());
	    dlm->status = DLMOD_ERROR;
	    return;
	}
    } else {
	for (p = strtok(dlmod_path, ":"); p; p = strtok(NULL, ":")) {
	    snprintf(tmp_path, sizeof(tmp_path), "%s/%s.so", p, dlm->path);
#if 1
	    DEBUGMSGTL(("dlmod", "p: %s tmp_path: %s\n", p, tmp_path));
#endif
	    dlm->handle = dlopen(tmp_path, RTLD_NOW);
	    if (dlm->handle == NULL) {
		snprintf(dlm->error, sizeof(dlm->error), 
			 "dlopen failed: %s", dlerror());
		dlm->status = DLMOD_ERROR;
	    }
	}
	strncpy(dlm->path, tmp_path, sizeof(dlm->path));
	if (dlm->status == DLMOD_ERROR) 
	    return;
    }
    snprintf(sym_init, sizeof(sym_init), "_dynamic_init_%s", dlm->name);
    dl_init = dlsym(dlm->handle, sym_init);
    if (dl_init == NULL) {
	snprintf(dlm->error, sizeof(dlm->error), 
		 "dlsym failed: can't find \'%s\'", sym_init);
	dlm->status = DLMOD_ERROR;
	return;
    }
	
    if (dl_init()) {
	snprintf(dlm->error, sizeof(dlm->error), "\'%s\' failed", sym_init);
	dlm->status = DLMOD_ERROR;
	return;
    }

    dlm->error[0] = '\0';
    dlm->status = DLMOD_LOADED;
}

void 
dlmod_unload_module (struct dlmod *dlm)
{
    char sym_deinit[64];
    char buf[256];
    int (* dl_deinit)(void);
  
    if (!dlm || dlm->status != DLMOD_LOADED) 
	return;

    snprintf(sym_deinit, sizeof(sym_deinit), "_dynamic_deinit_%s", dlm->name);
    dl_deinit = dlsym(dlm->handle, sym_deinit);
    if (dl_deinit == NULL) {
	/** it's right way ? */
	dlm->status = DLMOD_ERROR;
	return;
    }
    dl_deinit();
    dlclose(dlm->handle);
    dlm->status = DLMOD_UNLOADED;
#if 1
    DEBUGMSGTL(("dlmod", "Module %s unloaded\n", dlm->name));
#endif
}

int
dlmod_get_next_index (void)
{
    return dlmod_next_index;
}

struct dlmod *
dlmod_get_by_index (int iindex)
{
    struct dlmod *dlmod;
  
    for (dlmod = dlmods; dlmod; dlmod=dlmod->next) 
	if (dlmod->index == iindex)
	    return dlmod;

    return NULL;
}
#if 0
struct dlmod *
dlmod_get_next(struct dlmod *dlm)
{
    if (dlm) 
	return dlmods;
    else
	return dlm->next;
}
#endif
