/* This file deals with (you guessed it) dynamic loading.*/

/*
  This file is part of Japhar, the GNU Virtual Machine for Java Bytecodes.
  Japhar is a project of The Hungry Programmers, GNU, and OryxSoft.

  Copyright (C) 1997, 1998, 1999 The Hungry Programmers

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Library General Public License for more details.

  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <dynamic_loading.h>
#include <log.h>
#include "jni.h"
#include "native-threads.h"
#include "compat.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#if HAVE_DLFCN_H
#include <dlfcn.h>
#endif
#if HAVE_DL_H
#include <dl.h>
#endif
#if defined(HAVE_WINDOWS_H)
#include <windows.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>

#define MYLOG "ArchDLL"

#if defined(HAVE_DLOPEN) && defined(HAVE_DLCLOSE) && defined(HAVE_DLSYM) && defined(HAVE_DLERROR)
#  define HAVE_UNIX_DLL
#  define DLL_ENDING ".so"
#  define DLL_PATH_ENV "LD_LIBRARY_PATH"
#  define DLL_PATH_SEPARATOR ':'
#  define DLL_SLASH "/"
#  define DLL_ERRORSTR dlerror()
#elif defined(HAVE_SHL_LOAD) && defined(HAVE_SHL_UNLOAD) && defined(HAVE_SHL_FINDSYM)
#  define HAVE_HPUX_DLL
#  define DLL_ENDING "sl"
#  define DLL_PATH_ENV "SHLIB_PATH"
#  define DLL_PATH_SEPARATOR ':'
#  define DLL_SLASH "/"
/* Hm, is this correct? [pere 1998-07-06] */
#  define DLL_ERRORSTR strerror(errno)
#elif defined(HAVE_LOADLIBRARY) && defined(HAVE_FREELIBRARY) && defined(HAVE_GETPROCADDRESS)
/* XXX This is completely untested.  I have no idea what I am doing. :-) */
#  define HAVE_WIN32_DLL
#  define DLL_ENDING ".dll"
#  define DLL_PATH_ENV "PATH"
#  define DLL_PATH_SEPARATOR ';'
#  define DLL_SLASH "\\"
#  define DLL_ERRORSTR "[ I have no idea! ]"
#else
#  error "Unsupported DLL API.  Port me"
#endif

#if (!defined(HAVE_UNIX_DLL) && !defined(HAVE_HPUX_DLL) && !defined(HAVE_WIN32_DLL)) /*|| defined PROFILING*/
#undef USE_DLL
#else
#  ifndef USE_DLL
#    define USE_DLL
#  endif
#endif

#if ! defined(USE_DLL)
extern sym_mapping java_lang_mappings[];
extern int num_java_lang_mappings;

extern sym_mapping java_lang_reflect_mappings[];
extern int num_java_lang_reflect_mappings;

extern sym_mapping java_io_mappings[];
extern int num_java_io_mappings;

extern sym_mapping java_net_mappings[];
extern int num_java_net_mappings;

extern sym_mapping java_util_mappings[];
extern int num_java_util_mappings;

extern sym_mapping java_util_zip_mappings[];
extern int num_java_util_zip_mappings;

extern sym_mapping sysres_mappings[];
extern int num_sysres_mappings;

/* JDK1.2 */
extern sym_mapping java_sec_mappings[];
extern int num_java_sec_mappings;

#ifdef HAVE_MOTIF
extern sym_mapping sun_awt_motif_mappings[];
extern int num_sun_awt_motif_mappings;
#endif
#endif

typedef struct library_cache_entry {
  struct library_cache_entry *prev;
  struct library_cache_entry *next;

  char *library_name;
  DLL_LibHandle library_handle;
} library_cache_entry;

static library_cache_entry* cache = NULL;

/* adds a library to the cache. */
static void 
add_to_library_cache(char *lib_name,
		     DLL_LibHandle handle)
{
  library_cache_entry *new_entry = (library_cache_entry*)calloc(1, sizeof(library_cache_entry));

  new_entry->library_name = strdup(lib_name);
  new_entry->library_handle = handle;

  if (cache)
    cache->prev = new_entry;
  new_entry->next = cache;
  cache = new_entry;
}

static void
remove_from_library_cache(DLL_LibHandle handle)
{
  library_cache_entry *cur;

  for (cur = cache;
       cur != NULL;
       cur = cur->next)
    {
      if (cur->library_handle == handle)
	{
	  if (cur->prev)
	    cur->prev->next = cur->next;
	  if (cur->next)
	    cur->next->prev = cur->prev;

	  if (cache == cur)
	    cache = cur->next;

	  free (cur->library_name);
	  free (cur);
	}
    }
}

static DLL_LibHandle
library_in_cache(char *lib_name)
{
  library_cache_entry *cur;

  for (cur = cache;
       cur != NULL;
       cur = cur->next)
    {
      if (!strcmp(lib_name, cur->library_name))
	return cur->library_handle;
    }

  return NULL;
}

/**
 * loads in a shared library and returns a handle to it.
 * this handle is used in all future references to the
 * library.
 *
 * NULL should be returned in the event of failure.
 */
DLL_LibHandle
DLL_load(char *lib_path)
{
  DLL_LibHandle handle = NULL;

  JAVARLOG1(MYLOG, 1, "Loading shared library %s\n", lib_path);

  /* check if it's in our cache. */
  handle = library_in_cache(lib_path);

  /* if so, just return the handle to it. */
  if (handle != NULL)
    return handle;

#if defined USE_DLL

  {
    struct stat stat_buf;

    if (stat(lib_path, &stat_buf) == -1)
      return NULL;
  }

#if defined(HAVE_UNIX_DLL)
  /* if it's not, we attempt to load it. */
  handle = dlopen(lib_path, RTLD_LAZY);
#elif defined(HAVE_HPUX_DLL)
  handle = shl_load(lib_path, BIND_DEFERRED, 0L);
#elif defined(HAVE_WIN32_DLL)
  handle = LoadLibrary(lib_path);
#else
#  error "Port me"
#endif

  /* we only add it to the cache if the load succeeded */
  if (handle != NULL)
    add_to_library_cache(lib_path, handle);
  else
    JAVARLOG1(MYLOG, 3, "%s\n", DLL_ERRORSTR);

#else /* ! USE_DLL */
  handle = (DLL_LibHandle)calloc(1, sizeof(struct DLL_LibHandle));

  if (!strcmp(lib_path, "lang"))
    {
      handle->mappings = java_lang_mappings;
      handle->num_mappings = num_java_lang_mappings;
    }
  else if (strcmp(lib_path, "io"))
    {
      handle->mappings = java_io_mappings;
      handle->num_mappings = num_java_io_mappings;
    }
  else if (strcmp(lib_path, "net"))
    {
      handle->mappings = java_net_mappings;
      handle->num_mappings = num_java_net_mappings;
    }
  else if (strcmp(lib_path, "util"))
    {
      handle->mappings = java_util_mappings;
      handle->num_mappings = num_java_util_mappings;
    }
  else if (strstr(lib_path, "zip"))
    {
      handle->mappings = java_util_zip_mappings;
      handle->num_mappings = num_java_util_zip_mappings;
    }
  else if (strcmp(lib_path, "security"))
    {
      handle->mappings = java_sec_mappings;
      handle->num_mappings = num_java_sec_mappings;
    }
  else if (strcmp(lib_path, "sysresource"))
    {
      handle->mappings = sysres_mappings;
      handle->num_mappings = num_sysres_mappings;
    }
#ifdef HAVE_MOTIF
  else if (strstr(lib_path, "awt"))
    {
      handle->mappings = sun_awt_motif_mappings;
      handle->num_mappings = num_sun_awt_motif_mappings;
    }
#endif

  /* we only add it to the cache if the load succeeded */
  add_to_library_cache(lib_path, handle);

#endif
  return handle;
}

char *
DLL_mapLibName(char *lib_name)
{
  char *mapped_name;

  mapped_name = (char*)malloc(strlen("libjaphar_")
			      + strlen(lib_name)
			      + strlen(DLL_ENDING) + 1);

  sprintf (mapped_name, "libjaphar_%s" DLL_ENDING, lib_name);

  return mapped_name;
}

#ifdef USE_DLL
static DLL_LibHandle
DLL_findAlongPath(char *lib_name, char *path)
{
  DLL_LibHandle handle = NULL;
  char *lib_path_entry;
  char *colon;
  char *full_lib_name;

  lib_path_entry = path;
  colon = strchr(lib_path_entry, DLL_PATH_SEPARATOR);

  while (lib_path_entry[0] != '\0')
    {
      if (colon)
	*colon = '\0'; /* we'll set this back shortly. */

      full_lib_name = (char*)malloc(strlen(lib_path_entry)
				    + strlen("libjaphar_")
				    + strlen(lib_name)
				    + strlen(DLL_ENDING) + 2);
      sprintf (full_lib_name, "%s" DLL_SLASH "libjaphar_%s" DLL_ENDING,
	       lib_path_entry, lib_name);

      handle = DLL_load(full_lib_name);

      /* try finding lib%s.so if libjaphar_%s.so was not found. */
      if (handle == NULL)
	{
	  sprintf (full_lib_name, "%s" DLL_SLASH "lib%s" DLL_ENDING,
		   lib_path_entry, lib_name);

	  handle = DLL_load(full_lib_name);
	}

      free(full_lib_name);

      if (handle != NULL)
	{
	  if (colon)
	    *colon = DLL_PATH_SEPARATOR;
	  break;
	}
      else
	{
	  if (colon)
	    {
	      *colon = DLL_PATH_SEPARATOR;
	      lib_path_entry = colon + 1;
	      colon = strchr(lib_path_entry, DLL_PATH_SEPARATOR);
	    }
	  else
	    break;
	}
    }

  return handle;
}
#endif

DLL_LibHandle
DLL_find(char *lib_name)
{
  DLL_LibHandle handle = NULL;
#if defined USE_DLL
  static char *LD_LIBRARY_PATH;

  if (!LD_LIBRARY_PATH)
    LD_LIBRARY_PATH = getenv(DLL_PATH_ENV);
  

  if (LD_LIBRARY_PATH)
    handle = DLL_findAlongPath(lib_name, LD_LIBRARY_PATH);

  if (handle == NULL
      && (!LD_LIBRARY_PATH || strcmp(LD_LIBRARY_PATH, LIBDIR)))
    {
      handle = DLL_findAlongPath(lib_name, LIBDIR);
    }

#else /* ! USE_DLL */
  handle = DLL_load(lib_name);
#endif

  return handle;
}

void
DLL_unload(DLL_LibHandle handle)
{
#if defined USE_DLL
  int close_val;

#ifdef HAVE_UNIX_DLL
  close_val = dlclose(handle);
#elif defined(HAVE_HPUX_DLL)
  close_val = shl_unload(handle);
#elif defined(HAVE_WIN32_DLL)
  close_val = FreeLibrary(handle);
#else
#  error "Port me!"
#endif
  
#if defined(DEBUG)
  if (close_val != 0)
    fprintf(stderr, "DLL error = %s\n", DLL_ERRORSTR);
#endif

#else /* ! USE_DLL */
  free(handle);
#endif

  remove_from_library_cache(handle);
}

DLL_FuncHandle
DLL_findFunctionInLib(char *func_name,
		      DLL_LibHandle handle)
{
#if defined USE_DLL
  char buf[1024];

  snprintf(buf, sizeof(buf), "%s%s",
#ifdef NEED_UNDERSCORE
	   "_",
#else
	   "",
#endif
	   func_name);

#if defined(HAVE_UNIX_DLL)
  return dlsym(handle, buf);
#elif defined(HAVE_HPUX_DLL)
  {
    shl_t rethandle = handle;
    void *func;
    shl_findsym(&rethandle, buf, TYPE_UNDEFINED, &func);
    return func;
  }
#elif defined(HAVE_WIN32_DLL)
  return GetProcAddress(handle, buf);
#else
#  error "Port me!"
#endif

#else /* ! USE_DLL */
  int i;

  for (i = 0; i < handle->num_mappings; i ++)
    {
      if (!strcmp(handle->mappings[i].name, func_name))
	return handle->mappings[i].func_ptr;
    }

  return NULL;
#endif
}

static void
get_symbols_from_executable(void)
{
#if defined USE_DLL
  static int only_one_time = 0;

  if (!only_one_time) 
    {
      DLL_LibHandle handle = NULL;

      only_one_time = 1;

#if defined(HAVE_UNIX_DLL)
      handle = dlopen(NULL, 1);
#elif defined(HAVE_HPUX_DLL)
      handle = PROG_HANDLE;
#elif defined(HAVE_WIN32_DLL)
      handle = GetModuleHandle(NULL);
      add_to_library_cache ("JapharJVM.dll", GetModuleHandle("JapharJVM"));
#else
#  error "port me"
#endif

      add_to_library_cache ("<exec>", handle);
    }
#endif
}

DLL_FuncHandle
DLL_findFunction(char *func_name)
{
  library_cache_entry *cur;
  DLL_FuncHandle handle = NULL;

  get_symbols_from_executable();

  for (cur = cache;
       cur != NULL;
       cur = cur->next)
    {
      JAVARLOG2(MYLOG, 2, "Is %s() in library %s ?\n", func_name, cur->library_name);
      handle = DLL_findFunctionInLib(func_name,
				     cur->library_handle);

      if (handle != NULL)
	{
	  JAVARLOG0(MYLOG, 2, " - yep\n");
	  return handle;
	}

      JAVARLOG0(MYLOG, 2, " - nope\n");
    }

  return NULL;
}
