/* modfuncs.c: -*- C -*-  Dynamically loaded Meta-HTML modules. */

/*  Copyright (c) 1996 Brian J. Fox
    Author: Brian J. Fox (bfox@ai.mit.edu) Tue Dec 24 10:06:54 1996.

   This file is part of <Meta-HTML>(tm), a system for the rapid
   deployment of Internet and Intranet applications via the use of the
   Meta-HTML language.

   Copyright (c) 1995, 1996, Brian J. Fox (bfox@ai.mit.edu).
   Copyright (c) 1996, Universal Access Inc. (http://www.ua.com).

   Meta-HTML is free software; you can redistribute it and/or modify
   it under the terms of the UAI Free Software License as published
   by Universal Access Inc.; either version 1, or (at your option) any
   later version.

   This program 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
   UAI Free Software License for more details.

   You should have received a copy of the UAI Free Software License
   along with this program; if you have not, you may obtain one by
   writing to:

   Universal Access Inc.
   129 El Paseo Court
   Santa Barbara, CA
   93101  */

#include "language.h"
#include <dlfcn.h>

#if !defined (RTLD_NOW)
#  define RTLD_NOW 1
#endif

static void pf_load_module (PFunArgs);

static PFunDesc function_table[] = {
  { "LOAD-MODULE",	0, 0, pf_load_module },
  { (char *)NULL,	0, 0, (PFunHandler *)NULL }
};

PACKAGE_INITIALIZER (initialize_module_functions)
void
initialize_module_functions (Package *package)
{
  register int i;
  Symbol *sym;

  for (i = 0; function_table[i].tag != (char *)NULL; i++)
    {
      sym = symbol_intern_in_package (package, function_table[i].tag);
      sym->type = symtype_FUNCTION;
      sym->values = (char **)(&function_table[i]);
    }

  /* This version of Meta-HTML can handle dynamically loaded modules. */
  pagefunc_set_variable ("mhtml::module-capable", "true");
}

static char *static_dirs[3] =
 { "/opt/metahtml/lib", "/opt/metahtml/modules", (char *)NULL };

static char *
fully_qualified_module_name (char *name)
{
  int namelen = strlen (name);

  if ((*name != '/') && (namelen < 1024))
    {
      register int i;
      static char buffer[2048];
      char **dirs = symbol_get_values ("mhtml::module-directories");
      struct stat finfo;
      int add_so = 0;

      if ((namelen < 3) || (strcmp (name + (namelen - 3), ".so") != 0))
	add_so++;

      if (dirs == (char **)NULL)
	dirs = &static_dirs[0];

      for (i = 0; dirs[i] != (char *)NULL; i++)
	{
	  sprintf (buffer, "%s/%s", dirs[i], name);
	  if (add_so) strcat (buffer, ".so");

	  if (stat (buffer, &finfo) == 0)
	    return (strdup (buffer));
	}
    }

  return (strdup (name));
}

#define module_recent_error(file) \
	page_syserr ("LOAD-MODULE: (%s) %s", file, dlerror ())

#define MODULE_SYM_NAME "mhtml::loaded-modules"

typedef void VFUN (void);

static void
pf_load_module (PFunArgs)
{
  char *module_name = mhtml_evaluate_string (get_positional_arg (vars, 0));
  char *result = (char *)NULL;

  if (module_name != (char *)NULL)
    {
      register int i;
      char **modules = symbol_get_values (MODULE_SYM_NAME);
      char *fqn = fully_qualified_module_name (module_name);

      if (modules != (char **)NULL)
	{
	  for (i = 0; modules[i] != (char *)NULL; i++)
	    if (strcmp (fqn, modules[i]) == 0)
	      {
		result = "true";
		break;
	      }
	}

      if (result == (char *)NULL)
	{
	  void *handle = (void *)dlopen (fqn, RTLD_NOW);

	  if (handle != (void *)NULL)
	    {
	      char *no_init = mhtml_evaluate_string
		(get_value (vars, "noinitialize"));

	      /* Some operating systems lie.  For example, FreeBSD
		 says that dlsym works.  But it apparently doesn't.
		 Don't know why, can't say how.  It ALWAYS calls
		 _init() though, so I guess we can just change all
		 of our loadable libraries to have that function.
		 Can you say "UGH?" */
#if defined (__FreeBSD__)
	      if (no_init == (char *)NULL)
		no_init = strdup ("true");
#endif
	      if (no_init == (char *)NULL)
		{
		  VFUN *initfunc = (VFUN *)
		    dlsym (handle, "module_initialize");

		  if (initfunc != (VFUN *)NULL)
		    {
		      (*initfunc) ();
		      result = "true";
		    }
		  else
		    {
		      module_recent_error (fqn);
		      dlclose (handle);
		    }
		}
	      else
		result = "true";

	      if (result != (char *)NULL)
		{
		  Symbol *sym = symbol_intern (MODULE_SYM_NAME);
		  symbol_add_value (sym, fqn);
		}

	      xfree (no_init);
	    }
	  else
	    {
	      /* Error opening library. */
	      module_recent_error (module_name);
	    }
	}

      free (module_name);
      free (fqn);
    }

  if (result != (char *)NULL)
    {
      bprintf_insert (page, start, "%s", result);
      *newstart += strlen (result);
    }
}
