/* -*- Mode: C; c-file-style:"gnu"; indent-tabs-mode:nil -*-
   dynamic_loading.c -- if fmodf() is missing, make a replacement
   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 "arch.h"
#include "qop.h"
#include "compat.h"
#include "ltdl.h"

#include "nspr.h"
#include "plstr.h"

extern PRLogModuleInfo *dllLm;

#define DLL_PATH_ENV "LTDL_LIBRARY_PATH"

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

  char *library_name;
  HVMDllLibHandle library_handle;
} library_cache_entry;

static library_cache_entry* cache = NULL;
static PRMonitor* dll_monitor = NULL;

/* adds a library to the cache. Make sure dll_monitor is locked when
   this is called */
static void 
add_to_library_cache(char *lib_name,
                     HVMDllLibHandle handle)
{
  library_cache_entry *new_entry = PR_CALLOC(sizeof(library_cache_entry));

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

  ENQUEUE(new_entry, cache);
}

/* remove a library from the cache. Make sure dll_monitor is locked
   when this is called */
static void
remove_from_library_cache(HVMDllLibHandle handle)
{
  library_cache_entry *cur;

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

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

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

  for (cur = cache;
       cur != NULL;
       cur = cur->next)
    {
      if (!PL_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.
 */
PR_IMPLEMENT(HVMDllLibHandle)
HVM_DllLoad(char *lib_path)
{
  HVMDllLibHandle handle = NULL;

  PR_EnterMonitor(dll_monitor);
  PR_LOG(dllLm, PR_LOG_DEBUG, ("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, else try to load it. */
  if (handle == NULL)
    {
      handle = (HVMDllLibHandle) lt_dlopenext(lib_path);

      /* we only add it to the cache if the load succeeded */
      if (handle != NULL)
        add_to_library_cache(lib_path, handle);
      else
        PR_LOG(dllLm, PR_LOG_DEBUG, ("%s\n", lt_dlerror()));
    }

  PR_ExitMonitor(dll_monitor);
  return handle;
}

PR_IMPLEMENT(char*)
HVM_DllMapLibName(char *lib_name)
{
  char *mapped_name = PR_smprintf("libjaphar_%s.so", lib_name);

  return mapped_name;
}

static HVMDllLibHandle
HVM_DllFindAlongPath(char *lib_name, char *path)
{
  HVMDllLibHandle handle = NULL;
  char *lib_path_entry;
  char *colon;
  char *full_lib_name;

  lib_path_entry = path;
  colon = PL_strstr(lib_path_entry, PATH_SEPARATOR);

  while (lib_path_entry[0] != '\0')
    {
      if (colon)
        *colon = '\0'; /* we'll set this back shortly. */
      
      full_lib_name = PR_smprintf("%s" DIR_SEPARATOR "libjaphar_%s", lib_path_entry, lib_name);
      
      handle = HVM_DllLoad(full_lib_name);
      
      /* try finding lib%s if libjaphar_%s was not found. */
      if (handle == NULL)
        {
          sprintf (full_lib_name, "%s" DIR_SEPARATOR "lib%s",
                   lib_path_entry, lib_name);
          
          handle = HVM_DllLoad(full_lib_name);
        }
      
      PR_DELETE(full_lib_name);
      
      if (handle != NULL)
        {
          if (colon)
            *colon = PATH_SEPARATOR[0];
          break;
        }
      else
        {
          if (colon)
            {
              *colon = PATH_SEPARATOR[0];
              lib_path_entry = colon + PL_strlen(PATH_SEPARATOR);
              colon = PL_strstr(lib_path_entry, PATH_SEPARATOR);
            }
          else
            break;
        }
    }
  
  return handle;
}

PR_IMPLEMENT(HVMDllLibHandle)
HVM_DllFind(char *lib_name)
{
  HVMDllLibHandle handle = NULL;
  static char *LD_LIBRARY_PATH;

  PR_EnterMonitor(dll_monitor);

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

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

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

  PR_ExitMonitor(dll_monitor);

  return handle;
}

void
HVM_DllUnload(HVMDllLibHandle handle)
{
  int close_val;

  PR_EnterMonitor(dll_monitor);
  close_val = lt_dlclose(handle);
  
#if defined(DEBUG)
  if (close_val != 0)
    fprintf(stderr, "DLL error = %s\n", lt_dlerror());
#endif

  remove_from_library_cache(handle);

  PR_ExitMonitor(dll_monitor);
}

PR_IMPLEMENT(HVMDllFuncHandle)
HVM_DllFindFunctionInLib(char *func_name,
                         HVMDllLibHandle handle)
{
  HVMDllFuncHandle retval = NULL;

  PR_EnterMonitor(dll_monitor);

  retval = lt_dlsym(handle, func_name);

  PR_ExitMonitor(dll_monitor);

  return retval;
}

static void
get_symbols_from_executable(void)
{
  static int only_one_time = 0;
  
  if (!only_one_time) 
    {
      HVMDllLibHandle handle = NULL;

      only_one_time = 1;

      handle = lt_dlopen(NULL);
      if (NULL != handle)
        add_to_library_cache ("<exec>", handle);
    }
}

PR_IMPLEMENT(HVMDllFuncHandle)
HVM_DllFindFunction(char *func_name)
{
  library_cache_entry *cur;
  HVMDllFuncHandle handle = NULL;

  PR_EnterMonitor(dll_monitor);

  get_symbols_from_executable();

  for (cur = cache;
       cur != NULL;
       cur = cur->next)
    {
      PR_LOG(dllLm, PR_LOG_DEBUG, ("Is %s() in library %s ?\n",
                                   func_name, cur->library_name));
      handle = HVM_DllFindFunctionInLib(func_name,
                                        cur->library_handle);

      if (handle != NULL)
        {
          PR_LOG(dllLm, PR_LOG_DEBUG, (" - yep\n"));
          break;
        }

      PR_LOG(dllLm, PR_LOG_DEBUG, (" - nope\n"));
    }

  PR_ExitMonitor(dll_monitor);
  return handle;
}

PR_IMPLEMENT(PRBool)
HVM_DllInit(void)
{
  int retval = 0;
  if (NULL == dll_monitor)
    {
      dll_monitor = PR_NewMonitor();
    }
  PR_EnterMonitor(dll_monitor);
  retval = lt_dlinit();
  PR_ExitMonitor(dll_monitor);
  return (retval == 0);
}

PR_IMPLEMENT(void)
HVM_DllExit(void)
{
  if (NULL == dll_monitor)     /* Not a single DLL was loaded ==> */
    {
      PR_LOG(dllLm, PR_LOG_DEBUG,
             ("DllExit() called with NULL == dll_monitor\n"));
      return;
    }
  PR_EnterMonitor(dll_monitor);
  lt_dlexit();
  PR_ExitMonitor(dll_monitor);
  PR_DestroyMonitor(dll_monitor);
  dll_monitor = NULL;
}
