/*
 *  dlf.c
 *
 *  $Id: dlf.c,v 1.4 1999/06/01 15:31:40 VZ Exp $
 *
 *  Dynamic Library Loader (mapping to SVR4)
 *
 *  The iODBC driver manager.
 *  
 *  Copyright (C) 1995 by Ke Jin <kejin@empress.com> 
 *
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include	"dlf.h"
#include	<errno.h>

#ifdef	DLDAPI_DEFINED
#undef DLDAPI_DEFINED
#endif

#ifdef	DLDAPI_SVR4_DLFCN
#define DLDAPI_DEFINED
static char sccsid[] = "@(#)dynamic load interface -- SVR4 dlfcn";
#endif

/********************************* 
 *
 *	HP/UX 
 *
 *********************************/

#ifdef	DLDAPI_HP_SHL
#define	DLDAPI_DEFINED
#include	<dl.h>

static char sccsid[] = "@(#)dynamic load interface -- HP/UX dl(shl)";

void *
dlopen (char *path, int mode)
{
  return (void *) shl_load ((char *) (path), BIND_DEFERRED, 0L);
}


void *
dlsym (void *hdll, char *sym)
{
  void *symaddr = 0;
  int ret;

  if (!hdll)
    hdll = (void *) PROG_HANDLE;

  /* Remember, a driver may export calls as function pointers 
   * (i.e. with type TYPE_DATA) rather than as functions 
   * (i.e. with type TYPE_PROCEDURE). Thus, to be safe, we 
   * uses TYPE_UNDEFINED to cover all of them. 
   */
  ret = shl_findsym ((shl_t *) & hdll, sym, TYPE_UNDEFINED, &symaddr);

  if (ret == -1)
    return 0;

  return symaddr;
}


char *
dlerror ()
{
  extern char *strerror ();

  return strerror (errno);
}


int 
dlclose (void *hdll)
{
  return shl_unload ((shl_t) hdll);
}
#endif /* end of HP/UX Seection */


/*********************************
 *
 *	IBM AIX
 *
 *********************************/

#ifdef	DLDAPI_AIX_LOAD
#define	DLDAPI_DEFINED
#include 	<sys/types.h>
#include	<sys/ldr.h>
#include	<sys/stat.h>
#include	<nlist.h>

/*
 *   Following id sting is a copyright mark. Removing(i.e. use the 
 *   source code in this .c file without include it or make it not
 *   appear in the final object file of AIX platform) or modifing 
 *   it without permission from original author(kejin@empress.com) 
 *   are copyright violation.
 */
static char sccsid[]
= "@(#)dynamic load interface, Copyright(c) 1995 by Ke Jin";

#ifndef	HTAB_SIZE
#define	HTAB_SIZE	256
#endif

#define	FACTOR		0.618039887	/* i.e. (sqrt(5) - 1)/2 */

#ifndef	ENTRY_SYM
#define	ENTRY_SYM	".__start"	/* default entry point for aix */
#endif

typedef struct slot_s
  {
    char *sym;
    long fdesc[3];		/* 12 bytes function descriptor */
    struct slot_s *next;
  }
slot_t;

/* Note: on AIX, a function pointer actually points to a
 * function descriptor, a 12 bytes data. The first 4 bytes
 * is the virtual address of the function. The next 4 bytes 
 * is the virtual address of TOC (Table of Contents) of the
 * object module the function belong to. The last 4 bytes 
 * are always 0 for C and Fortran functions. Every object 
 * module has an entry point (which can be specified at link
 * time by -e ld option). iODBC driver manager requires ODBC
 * driver shared library always use the default entry point
 * (so you shouldn't use -e ld option when creating a driver
 * share library). load() returns the function descriptor of 
 * a module's entry point. From which we can calculate function
 * descriptors of other functions in the same module by using
 * the fact that the load() doesn't change the relative 
 * offset of functions to their module entry point(i.e the 
 * offset in memory loaded by load() will be as same as in 
 * the module library file). 
 */

typedef slot_t *hent_t;
typedef struct nlist nlist_t;
typedef struct stat stat_t;

typedef struct obj
  {
    int dev;			/* device id */
    int ino;			/* inode number */
    char *path;			/* file name */
    int (*pentry) ();		/* entry point of this share library */
    int refn;			/* number of reference */
    hent_t htab[HTAB_SIZE];
    struct obj * next;
  }
obj_t;

static char *errmsg = 0;

static void 
init_htab (hent_t * ht)
/* initate a hashing table */
{
  int i;

  for (i = 0; i < HTAB_SIZE; i++)
    ht[i] = (slot_t *) 0;

  return;
}


static void 
clean_htab (hent_t * ht)
/* free all slots */
{
  int i;
  slot_t *ent;
  slot_t *tent;

  for (i = 0; i < HTAB_SIZE; i++)
    {
      for (ent = ht[i]; ent;)
	{
	  tent = ent->next;

	  free (ent->sym);
	  free (ent);

	  ent = tent;
	}

      ht[i] = 0;
    }

  return;
}


static int 
hash (char *sym)
{
  int a, key;
  double f;

  if (!sym || !*sym)
    return 0;

  for (key = *sym; *sym; sym++)
    {
      key += *sym;
      a = key;

      key = (int) ((a << 8) + (key >> 8));
      key = (key > 0) ? key : -key;
    }

  f = key * FACTOR;
  a = (int) f;

  return (int) ((HTAB_SIZE - 1) * (f - a));
}


static hent_t 
search (hent_t * htab, char *sym)
/* search hashing table to find a matched slot */
{
  int key;
  slot_t *ent;

  key = hash (sym);

  for (ent = htab[key]; ent; ent = ent->next)
    {
      if (!strcmp (ent->sym, sym))
	return ent;
    }

  return 0;			/* no match */
}


static void 
insert (hent_t * htab, slot_t * ent)
/* insert a new slot to hashing table */
{
  int key;

  key = hash (ent->sym);

  ent->next = htab[key];
  htab[key] = ent;

  return;
}


static slot_t *
slot_alloc (char *sym)
/* allocate a new slot with symbol */
{
  slot_t *ent;

  ent = (slot_t *) malloc (sizeof (slot_t));

  ent->sym = (char *) malloc (strlen (sym) + 1);

  if (!ent->sym)
    {
      free (ent);
      return 0;
    }

  strcpy (ent->sym, sym);

  return ent;
}


static obj_t *obj_list = 0;

void *
dlopen (char *file, int mode)
{
  stat_t st;
  obj_t *pobj;
  char buf[1024];

  if (!file || !*file)
    {
      errno = EINVAL;
      return 0;
    }

  errno = 0;
  errmsg = 0;

  if (stat (file, &st))
    return 0;

  for (pobj = obj_list; pobj; pobj = pobj->next)
    /* find a match object */
    {
      if (pobj->ino == st.st_ino
	  && pobj->dev == st.st_dev)
	{
	  /* found a match. increase its
	   * reference count and return 
	   * its address */
	  pobj->refn++;
	  return pobj;
	}
    }

  pobj = (obj_t *) malloc (sizeof (obj_t));

  if (!pobj)
    return 0;

  pobj->path = (char *) malloc (strlen (file) + 1);

  if (!pobj->path)
    {
      free (pobj);
      return 0;
    }

  strcpy (pobj->path, file);

  pobj->dev = st.st_dev;
  pobj->ino = st.st_ino;
  pobj->refn = 1;

  pobj->pentry = (int (*)()) load (file, 0, 0);

  if (!pobj->pentry)
    {
      free (pobj->path);
      free (pobj);
      return 0;
    }

  init_htab (pobj->htab);

  pobj->next = obj_list;
  obj_list = pobj;

  return pobj;
}


int 
dlclose (void *hobj)
{
  obj_t *pobj = (obj_t *) hobj;
  obj_t *tpobj;
  int match = 0;

  if (!hobj)
    {
      errno = EINVAL;
      return -1;
    }

  errno = 0;
  errmsg = 0;

  if (pobj == obj_list)
    {
      pobj->refn--;

      if (pobj->refn)
	return 0;

      match = 1;
      obj_list = pobj->next;
    }

  for (tpobj = obj_list; !match && tpobj; tpobj = tpobj->next)
    {
      if (tpobj->next == pobj)
	{
	  pobj->refn--;

	  if (pobj->refn)
	    return 0;

	  match = 1;
	  tpobj->next = pobj->next;
	}
    }

  if (match)
    {
      unload ((void *) (pobj->pentry));
      clean_htab (pobj->htab);
      free (pobj->path);
      free (pobj);
    }

  return 0;
}


char *
dlerror ()
{
  extern char *sys_errlist[];

  if (!errmsg || !errmsg[0])
    {
      if (errno >= 0)
	return sys_errlist[errno];

      return "";
    }

  return errmsg;
}


void *
dlsym (void *hdl, char *sym)
{
  nlist_t nl[3];
  obj_t *pobj = (obj_t *) hdl;
  slot_t *ent;
  int (*fp) ();
  long lbuf[3];

  if (!hdl || !(pobj->htab) || !sym || !*sym)
    {
      errno = EINVAL;
      return 0;
    }

  errno = 0;
  errmsg = 0;

  ent = search (pobj->htab, sym);

  if (ent)
    return ent->fdesc;

#define	n_name	_n._n_name

  nl[0].n_name = ENTRY_SYM;
  nl[1].n_name = sym;
  nl[2].n_name = 0;

  /* There is a potential problem here. If application
   * did not pass a full path name, and changed the
   * working directory after the load(), then nlist()
   * will be unable to open the original shared library
   * file to resolve the symbols. there are 3 ways to working 
   * round this: 1. convert to full pathname in driver
   * manager. 2. applications always pass driver's full 
   * path name. 3. if driver itself don't support 
   * SQLGetFunctions(), call it with SQL_ALL_FUNCTIONS
   * as flag immidately after SQLConnect(), SQLDriverConnect()
   * and SQLBrowseConnect() to force the driver manager
   * resolving all will be used symbols. 
   */
  if (nlist (pobj->path, nl) == -1)
    return 0;

  if (!nl[0].n_type && !nl[0].n_value)
    {
      errmsg = "can't locate module entry symbol";
      return 0;
    }

  /* Note: On AIX 3.x if the object library is not
   * built with -g compiling option, .n_type field
   * is always 0. While on 4.x it will be 32. 
   * On AIX 4.x, if the symbol is a entry point,
   * n_value will be 0. However, one thing is for sure 
   * that if a symbol is not existance in the file,
   * both .n_type and .n_value would be 0.
   */

  if (!nl[1].n_type && !nl[1].n_value)
    {
      errmsg = "symbol not existance in this module";
      return 0;
    }

  ent = slot_alloc (sym);

  if (!ent)
    return 0;

  /* catch it with a slot in the hashing table */
  insert (pobj->htab, ent);

  memcpy (ent->fdesc, pobj->pentry, sizeof (ent->fdesc));

  /* now ent->fdesc[0] is the virtual address of entry point
   * and ent->fdesc[1] is the TOC of the module
   */

  /* let's calculate the virtual address of the symbol 
   * by adding a relative offset getting from the module 
   * file symbol table, i.e
   *
   *  functin virtual address = entry point virtual address +
   *     + ( function offset in file - entry point offset in file )
   */

  (ent->fdesc)[0] = (ent->fdesc)[0] +
      (nl[1].n_value - nl[0].n_value);

  /* return the function descriptor */
  return ent->fdesc;
}
#endif /* end of IBM AIX Section */


/********************************* 
 *
 *	Windows 3.x, 95, NT
 *
 *********************************/

#ifdef	DLDAPI_WINDOWS
#define	DLDAPI_DEFINED
#include	<windows.h>

void FAR *
dlopen (char FAR * dll, int mode)
{
  HINSTANCE hint;

  if (dll == NULL)
    {
      return GetWindowWord (NULL, GWW_HINSTANCE);
    }

  hint = LoadLibrary (dll);

  if (hint < HINSTANCE_ERROR)
    {
      return NULL;
    }

  return (void FAR *) hint;
}


void FAR *
dlsym (void FAR * hdll, char FAR * sym)
{
  return (void FAR *) GetProcAddress (hdll, sym);
}


char FAR *
dlerror ()
{
  return 0L;			/* unimplemented yet */
}


int 
dlclose (void FAR * hdll)
{
  FreeLibrary ((HINSTANCE) hdll);
}
#endif /* end of Windows family */


/***********************************
 *
 * 	other platforms
 *
 ***********************************/

#ifdef DLDAPI_OS2
#define	DLDAPI_DEFINED
/*
 *    DosLoadModule(), DosQueryProcAddress(), DosFreeModule(), ...
 */
#endif

#ifdef	DLDAPI_MAC
#define	DLDAPI_DEFINED
#endif

#ifdef DLDAPI_NEXT
#define	DLDAPI_DEFINED
#endif

#ifndef DLDAPI_DEFINED
#error	"dynamic load editor undefined"
#endif
