//Copyright, 2001-2003, Astra Network Inc.  All Rights Reserved

//This source code has been published by Astra Network Inc. However, any
//use, reproduction, modification, distribution or transfer of this
//software, or any software which includes or is based upon any of this
//code, is only permitted if expressly authorized by a written license
//agreement from Astra. Contact your Astra representative directly for
//more information.

#include <dlfcn.h>

#include <dirent.h>
#ifndef sun
#include <sys/dir.h>
#endif
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <regex.h>
#include <string.h>
#include <errno.h>

#include "module_load.h"
#include "global_bits.h"
#include "olmod.h"

static size_t modtable_size = 0;
static size_t modtable_alloc = 0;

static char **getpkglist (char **directories);
extern void checkmodules (void);
extern void initmodules (void);
static int dirsort (const void *a, const void *b);
static void insert_module (olmod_module_t * newmod, char *library);
static modlist_entry_t *extend_modtable (void);

void
load_modules (char **moddirnames)
{
  void *dlpackage;
  olmod_module_t *pkgtable;
  modlist_entry_t *last_module;
  char **pkgnames;
  int i, j;

  if (verbose != 0)
    printf ("Reading built-in modules.\n");

  dlpackage = dlopen (NULL, RTLD_NOW);
  if (dlpackage == NULL)
    {
      fatal ("Could not dlopen self: %s", dlerror ());
    }

  pkgtable =
    (olmod_module_t *) dlsym (dlpackage, "object_global_overlord_table");
  if (pkgtable == NULL)
    {
      fatal ("Could not find module table symbol: %s", dlerror ());
    }

  for (j = 0; pkgtable[j].keyword != NULL; j++)
    insert_module (&pkgtable[j], NULL);

  (void) dlclose (dlpackage);

  if (moddirnames == NULL || moddirnames[0] == NULL)
    {
      if (verbose != 0)
	printf
	  ("No module directories given (-d dir), using builtins only.\n");
      checkmodules ();
      return;
    }

  pkgnames = getpkglist (moddirnames);

  for (i = 0; pkgnames[i] != NULL; i++)
    {
      if (verbose != 0)
	printf ("Found shared lib: %s\n", pkgnames[i]);

      dlpackage = dlopen (pkgnames[i], RTLD_NOW);
      if (dlpackage == NULL)
	{
	  printf ("Could not dlopen package %s: %s\n", pkgnames[i],
		  dlerror ());
	  free (pkgnames[i]);
	  continue;
	}

      pkgtable =
	(olmod_module_t *) dlsym (dlpackage, "object_global_overlord_table");
      if (pkgtable == NULL)
	{
	  printf ("No module table symbol in package %s: %s\n", pkgnames[i],
		  dlerror ());
	  (void) dlclose (dlpackage);
	  free (pkgnames[i]);
	  continue;
	}

      for (j = 0; pkgtable[j].keyword != NULL; j++)
	insert_module (&pkgtable[j], pkgnames[i]);

      (void) dlclose (dlpackage);
      free (pkgnames[i]);
    }
  last_module = extend_modtable ();
  last_module->module = NULL;
  last_module->dlhandle = NULL;

  checkmodules ();
}

// returns a list of shared object names from a list of directory names
// matches files with the pattern which starts with lib* and ends with .so
static char **
getpkglist (char **directories)
{
  static char *pkglist[256];
  int i, j, k = 0;
  regex_t filematch;
  DIR *curdir;

  (void) regcomp (&filematch, "^lib.*\\.so$", REG_NOSUB);

  // while we have directories to read
  for (i = 0; directories[i] != NULL; i++)
    {
      errno = 0;
      curdir = opendir (directories[i]);
      if (curdir == NULL)
	{
	  printf ("Could not read directory \"%s\": %s\n",
		  directories[i], strerror (errno));
	  continue;
	}

      // we have an open directory
      for (j = 0; j + k < 255;)
	{
	  struct dirent *curdirent;
	  struct stat filestat;

	  curdirent = readdir (curdir);
	  if (curdirent == NULL)
	    break;

	  if (regexec (&filematch, curdirent->d_name, 0L, NULL, 0L) != 0)
	    continue;

	  // we have a file which matches the pattern
	  pkglist[j + k] =
	    malloc (strlen (directories[i]) + strlen (curdirent->d_name) + 2);
	  sprintf (pkglist[j + k], "%s/%s", directories[i],
		   curdirent->d_name);

	  (void) stat (pkglist[j + k], &filestat);
	  // if the file is not a regular file, ignore it
	  if (!S_ISREG (filestat.st_mode))
	    {
	      free (pkglist[j + k]);
	      continue;
	    }
	  j++;
	}
      // sort the files from this directory into the list
      qsort (&pkglist[k], j, sizeof (char *), dirsort);
      k += j;
      (void) closedir (curdir);
    }
  pkglist[k] = NULL;
  regfree (&filematch);
  return pkglist;
}

static int
dirsort (const void *a, const void *b)
{
  return strcmp (*(char **) a, *(char **) b);
}

// insert a module into the modtable
static void
insert_module (olmod_module_t * newmod, char *library)
{
  modlist_entry_t *modentry = NULL;
  int i;

  for (i = 0; i < modtable_size; i++)
    {
      if (strcmp (modtable[i].module->keyword, newmod->keyword) == 0)
	{
	  modentry = &modtable[i];
	  break;
	}
    }

  if (verbose != 0 && library != NULL)
    printf ("Adding module \"%s\" from package \"%s\"",
	    newmod->keyword, library);
  if (verbose != 0 && library == NULL)
    printf ("Adding built-in module \"%s\"", newmod->keyword);

  if (modentry == NULL)
    {
      modentry = extend_modtable ();
      if (verbose != 0)
	printf ("\n");
    }
  else
    {
      if (modentry->dlhandle != NULL)
	(void) dlclose (modentry->dlhandle);
      if (verbose != 0)
	printf (" (replacing already loaded module)\n");
    }
  if (library != NULL)
    modentry->dlhandle = dlopen (library, RTLD_NOW);
  else
    modentry->dlhandle = NULL;
  modentry->module = newmod;
}

static modlist_entry_t *
extend_modtable (void)
{
  if (modtable == NULL)
    {
      modtable = malloc (32 * sizeof (modlist_entry_t));
      modtable_alloc = 32;
    }
  if ((modtable_size + 1) > modtable_alloc)
    {
      modtable =
	realloc (modtable, modtable_alloc * 2 * sizeof (modlist_entry_t));
      modtable_alloc *= 2;
    }
  if (modtable == NULL)
    {
      fatal ("Out of Memory while loading modules!");
    }
  return &modtable[modtable_size++];
}
