/******************************** -*- C -*- ****************************
 *
 *	AIX dynamic linking support code
 *
 *	$Revision: 1.7.5$
 *	$Date: 2000/05/28 16:56:52$
 *	$Author: pb$
 *
 ***********************************************************************/

/***********************************************************************
 *
 * Copyright (c) 1992,1993,1995,1996, Jens-Uwe Mager, Helios Software GmbH
 * Not derived from licensed software.
 *
 * Permission is granted to freely use, copy, modify, and redistribute
 * this software, provided that the author is not construed to be liable
 * for any results of using the software, alterations are clearly marked
 * as such, and this notice is not modified.
 *
 * This file is part of GNU Smalltalk.  It was modified to use the GNU
 * Smalltalk API for dynamic linking instead of dlopen/dlsym.
 *
 * GNU Smalltalk is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2, or (at your option) any later 
 * version.
 * 
 * GNU Smalltalk 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 General Public License for
 * more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * GNU Smalltalk; see the file COPYING.  If not, write to the Free Software
 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  
 *
 ***********************************************************************/


/* @(#)dlfcn.c	1.7 revision of 95/08/14  19:08:38
 * This is an unpublished work copyright (c) 1992 HELIOS Software GmbH
 * 30159 Hannover, Germany
 */

#include "dld_gst.h"
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ldr.h>
#include <a.out.h>
#include <ldfcn.h>

char	*gst_dld_exts[] = { ".so", ".a", NULL };

/* We simulate dlopen() et al. through a call to load. Because AIX has
 * no call to find an exported symbol we read the loader section of the
 * loaded module and build a list of exported symbols and their virtual
 * address. */

typedef struct {
  char    *name;		/* the symbols's name */
  void    *addr;		/* its relocated virtual address */
} Export, *ExportPtr;

/* xlC uses the following structure to list its constructors and
 * destructors. This is gleaned from the output of munch. */
typedef struct {
  void (*init)(void);		/* call static constructors */
  void (*term)(void);		/* call static destructors */
} Cdtor, *CdtorPtr;

/* The void * handle returned from dlopen is actually a ModulePtr. */
typedef struct Module {
  struct Module	 *next;
  char		 *name;		/* module name for refcounting */
  int		 refCnt;	/* the number of references */
  void		 *entry;	/* entry point from load */
  struct dl_info *info;		/* optional init/terminate functions */
  CdtorPtr	 cdtors;	/* optional C++ constructors */
  int		 nExports;	/* the number of exports found */
  ExportPtr	 exports;	/* the array of exports */
} Module, *ModulePtr;

/* We keep a list of all loaded modules to be able to call the fini
 * handlers and destructors at atexit() time. */
static ModulePtr modList;

/* The last error from one of the dl* routines is kept in static
 * variables here. Each error is returned only once to the caller. */
static char errbuf[BUFSIZ];
static int errvalid;

static void caterr();
static int readExports();
static void terminate();
static voidPtr findMain();
static int dlclose();

voidPtr
gst_dld_open (path)
     char *path; 
{
  register ModulePtr mp;
  static voidPtr mainModule;

  /* Upon the first call register a terminate handler that will
   * close all libraries. Also get a reference to the main module
   * for use with loadbind. */
  if (!mainModule) {
    if ((mainModule = findMain()) == NULL)
      return NULL;
    atexit(terminate);
  }
  /* Scan the list of modules if we have the module already loaded. */
  for (mp = modList; mp; mp = mp->next)
    if (strcmp(mp->name, path) == 0) {
      mp->refCnt++;
      return (voidPtr) mp;
    }
  if ((mp = (ModulePtr)xcalloc(1, sizeof(*mp))) == NULL) {
    errvalid++;
    strcpy(errbuf, strerror(errno));
    return (voidPtr) NULL;
  }
  mp->name = xmalloc((unsigned) (strlen(path) + 1));
  strcpy(mp->name, path);
  if ((mp->entry = (voidPtr)load(path, L_NOAUTODEFER, NULL)) == NULL) {
    xfree(mp->name);
    xfree(mp);
    errvalid++;
    strcpy(errbuf, path);
    strcat(errbuf, ": ");
    /* If AIX says the file is not executable, the error
     * can be further described by querying the loader about
     * the last error. */
    if (errno == ENOEXEC) {
      char *tmp[BUFSIZ/sizeof(char *)];
      if (loadquery(L_GETMESSAGES, tmp, sizeof(tmp)) == -1)
	strcat(errbuf, strerror(errno));
      else {
	char **p;
	for (p = tmp; *p; p++)
	  caterr(*p);
      }
    } else
      strcat(errbuf, strerror(errno));
    return (voidPtr) NULL;
  }
  mp->refCnt = 1;
  mp->next = modList;
  modList = mp;
  if (loadbind(0, mainModule, mp->entry) == -1) {
    dlclose(mp);
    errvalid++;
    strcpy(errbuf, strerror(errno));
    return (voidPtr) NULL;
  }
  if (readExports(mp) == -1) {
    dlclose(mp);
    return (voidPtr) NULL;
  }
  /* If there is a dl_info structure, call the init function. */
  if (mp->info = (struct dl_info *)gst_dld_sym(mp, "dl_info")) {
    if (mp->info->init)
      (*mp->info->init)();
  } else
    errvalid = 0;

  /* If the shared object was compiled using xlC we will need
   * to call static constructors (and later on dlclose destructors). */
  if (mp->cdtors = (CdtorPtr)gst_dld_sym(mp, "__cdtors")) {
    while (mp->cdtors->init) {
      (*mp->cdtors->init)();
      mp->cdtors++;
    }
  } else
    errvalid = 0;
  return (voidPtr) mp;
}

/* Attempt to decipher an AIX loader error message and append it
 * to our static error message buffer. */
void
caterr(s)
     char *s;
{
  register char *p = s;

  while (*p >= '0' && *p <= '9')
    p++;
  switch(atoi(s)) {
  case L_ERROR_TOOMANY:
    strcat(errbuf, "to many errors");
    break;
  case L_ERROR_NOLIB:
    strcat(errbuf, "can't load library");
    strcat(errbuf, p);
    break;
  case L_ERROR_UNDEF:
    strcat(errbuf, "can't find symbol");
    strcat(errbuf, p);
    break;
  case L_ERROR_RLDBAD:
    strcat(errbuf, "bad RLD");
    strcat(errbuf, p);
    break;
  case L_ERROR_FORMAT:
    strcat(errbuf, "bad exec format in");
    strcat(errbuf, p);
    break;
  case L_ERROR_ERRNO:
    strcat(errbuf, strerror(atoi(++p)));
    break;
  default:
    strcat(errbuf, s);
    break;
  }
}

voidPtr
gst_dld_sym (handle, symbol)
voidPtr handle;
     char *symbol;
{
  register ModulePtr mp = (ModulePtr)handle;
  register ExportPtr ep;
  register int i;

  /* Could speed up the search, but I assume that one assigns
   * the result to function pointers anyways. */
  for (ep = mp->exports, i = mp->nExports; i; i--, ep++)
    if (strcmp(ep->name, symbol) == 0)
      return ep->addr;
  errvalid++;
  strcpy(errbuf, "undefined symbol ");
  strcat(errbuf, symbol);
  return NULL;
}

char *
gst_dld_error ()
{
  if (errvalid) {
    errvalid = 0;
    return errbuf;
  }
  return NULL;
}

int
dlclose(handle)
     voidPtr handle;
{
  register ModulePtr mp = (ModulePtr)handle;
  int result;
  register ModulePtr mp1;

  if (--mp->refCnt > 0)
    return 0;
  if (mp->info && mp->info->fini)
    (*mp->info->fini)();
  if (mp->cdtors)
    while (mp->cdtors->term) {
      (*mp->cdtors->term)();
      mp->cdtors++;
    }
  result = unload(mp->entry);
  if (result == -1) {
    errvalid++;
    strcpy(errbuf, strerror(errno));
  }
  if (mp->exports) {
    register ExportPtr ep;
    register int i;
    for (ep = mp->exports, i = mp->nExports; i; i--, ep++)
      if (ep->name)
	xfree(ep->name);
    xfree(mp->exports);
  }
  if (mp == modList)
    modList = mp->next;
  else {
    for (mp1 = modList; mp1; mp1 = mp1->next)
      if (mp1->next == mp) {
	mp1->next = mp->next;
	break;
      }
  }
  xfree(mp->name);
  xfree(mp);
  return result;
}

void
terminate()
{
  while (modList)
    dlclose(modList);
}

/* Build the export table from the XCOFF .loader section. */
int
readExports(mp)
     ModulePtr mp;
{
  LDFILE *ldp = NULL;
  SCNHDR sh, shdata;
  LDHDR *lhp;
  char *ldbuf;
  LDSYM *ls;
  int i;
  ExportPtr ep;

  if ((ldp = ldopen(mp->name, ldp)) == NULL) {
    struct ld_info *lp;
    char *buf;
    int size = 4*1024;
    if (errno != ENOENT) {
      errvalid++;
      strcpy(errbuf, "readExports: ");
      strcat(errbuf, strerror(errno));
      return -1;
    }
    /* The module might be loaded due to the LIBPATH
     * environment variable. Search for the loaded
     * module using L_GETINFO. */
    if ((buf = xmalloc(size)) == NULL) {
      errvalid++;
      strcpy(errbuf, "readExports: ");
      strcat(errbuf, strerror(errno));
      return -1;
    }
    while ((i = loadquery(L_GETINFO, buf, size)) == -1 && errno == ENOMEM) {
      xfree(buf);
      size += 4*1024;
      if ((buf = xmalloc(size)) == NULL) {
	errvalid++;
	strcpy(errbuf, "readExports: ");
	strcat(errbuf, strerror(errno));
	return -1;
      }
    }
    if (i == -1) {
      errvalid++;
      strcpy(errbuf, "readExports: ");
      strcat(errbuf, strerror(errno));
      xfree(buf);
      return -1;
    }
    /* Traverse the list of loaded modules. The entry point
     * returned by load() does actually point to the data
     * segment origin. */
    lp = (struct ld_info *)buf;
    while (lp) {
      if (lp->ldinfo_dataorg == mp->entry) {
	ldp = ldopen(lp->ldinfo_filename, ldp);
	break;
      }
      if (lp->ldinfo_next == 0)
	lp = NULL;
      else
	lp = (struct ld_info *)((char *)lp + lp->ldinfo_next);
    }
    xfree(buf);
    if (!ldp) {
      errvalid++;
      strcpy(errbuf, "readExports: ");
      strcat(errbuf, strerror(errno));
      return -1;
    }
  }
  if (TYPE(ldp) != U802TOCMAGIC) {
    errvalid++;
    strcpy(errbuf, "readExports: bad magic");
    while(ldclose(ldp) == FAILURE)
      ;
    return -1;
  }
  /* Get the padding for the data section. This is needed for
   * AIX 4.1 compilers. This is used when building the final
   * function pointer to the exported symbol. */
  if (ldnshread(ldp, _DATA, &shdata) != SUCCESS) {
    errvalid++;
    strcpy(errbuf, "readExports: cannot read data section header");
    while(ldclose(ldp) == FAILURE)
      ;
    return -1;
  }
  if (ldnshread(ldp, _LOADER, &sh) != SUCCESS) {
    errvalid++;
    strcpy(errbuf, "readExports: cannot read loader section header");
    while(ldclose(ldp) == FAILURE)
      ;
    return -1;
  }
  /* We read the complete loader section in one chunk, this makes
   * finding long symbol names residing in the string table easier. */
  if ((ldbuf = (char *)xmalloc(sh.s_size)) == NULL) {
    errvalid++;
    strcpy(errbuf, "readExports: ");
    strcat(errbuf, strerror(errno));
    while(ldclose(ldp) == FAILURE)
      ;
    return -1;
  }
  if (FSEEK(ldp, sh.s_scnptr, BEGINNING) != OKFSEEK) {
    errvalid++;
    strcpy(errbuf, "readExports: cannot seek to loader section");
    xfree(ldbuf);
    while(ldclose(ldp) == FAILURE)
      ;
    return -1;
  }
  if (FREAD(ldbuf, sh.s_size, 1, ldp) != 1) {
    errvalid++;
    strcpy(errbuf, "readExports: cannot read loader section");
    xfree(ldbuf);
    while(ldclose(ldp) == FAILURE)
      ;
    return -1;
  }
  lhp = (LDHDR *)ldbuf;
  ls = (LDSYM *)(ldbuf+LDHDRSZ);

  /* Count the number of exports to include in our export table. */
  for (i = lhp->l_nsyms; i; i--, ls++) {
    if (!LDR_EXPORT(*ls))
      continue;
    mp->nExports++;
  }
  if ((mp->exports = (ExportPtr)xcalloc(mp->nExports, sizeof(*mp->exports))) == NULL) {
    errvalid++;
    strcpy(errbuf, "readExports: ");
    strcat(errbuf, strerror(errno));
    xfree(ldbuf);
    while(ldclose(ldp) == FAILURE)
      ;
    return -1;
  }
  /* Fill in the export table. All entries are relative to
   * the entry point we got from load. */
  ep = mp->exports;
  ls = (LDSYM *)(ldbuf+LDHDRSZ);
  for (i = lhp->l_nsyms; i; i--, ls++) {
    char *symname;
    char tmpsym[SYMNMLEN+1];
    if (!LDR_EXPORT(*ls))
      continue;
    if (ls->l_zeroes == 0)
      symname = ls->l_offset+lhp->l_stoff+ldbuf;
    else {
      /* The l_name member is not zero terminated, we
       * must copy the first SYMNMLEN chars and make
       * sure we have a zero byte at the end. */
      strncpy(tmpsym, ls->l_name, SYMNMLEN);
      tmpsym[SYMNMLEN] = '\0';
      symname = tmpsym;
    }
    ep->name = xmalloc((unsigned) (strlen(symname) + 1));
    strcpy(ep->name, symname);
    ep->addr = (voidPtr)((unsigned long)mp->entry +
	  ls->l_value - shdata.s_vaddr);
    ep++;
  }
  xfree(ldbuf);
  while(ldclose(ldp) == FAILURE)
    ;
  return 0;
}

/* Find the main modules entry point. This is used as export pointer
 * for loadbind() to be able to resolve references to the main part. */
voidPtr
findMain()
{
  struct ld_info *lp;
  char *buf;
  int size = 4*1024;
  int i;
  voidPtr ret;

  if ((buf = xmalloc(size)) == NULL) {
    errvalid++;
    strcpy(errbuf, "findMain: ");
    strcat(errbuf, strerror(errno));
    return NULL;
  }
  while ((i = loadquery(L_GETINFO, buf, size)) == -1 && errno == ENOMEM) {
    xfree(buf);
    size += 4*1024;
    if ((buf = xmalloc(size)) == NULL) {
      errvalid++;
      strcpy(errbuf, "findMain: ");
      strcat(errbuf, strerror(errno));
      return NULL;
    }
  }
  if (i == -1) {
    errvalid++;
    strcpy(errbuf, "findMain: ");
    strcat(errbuf, strerror(errno));
    xfree(buf);
    return NULL;
  }

  /* The first entry is the main module. The entry point
   * returned by load() does actually point to the data
   * segment origin. */
  lp = (struct ld_info *)buf;
  ret = lp->ldinfo_dataorg;
  xfree(buf);
  return ret;
}

int
gst_dld_init (dldArgv0)
char *dldArgv0;
{
  return 0;
}
