/* mp4h -- A macro processor for HTML documents
Copyright 2000-2002, Denis Barbier
All rights reserved.
This program 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.
This program is a work based on GNU m4 version 1.4n. Below is the
original copyright.
*/
/* GNU m4 -- A simple macro processor
Copyright (C) 1989, 90, 91, 92, 93, 94, 98 Free Software Foundation, Inc.
This program 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.
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
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "mp4h.h"
#include "builtin.h"
#ifdef WITH_MODULES
#define MODULES_UNINITIALISED -4444
#include "ltdl.h"
/*
* This file implements dynamic modules in GNU m4. A module is a
* compiled shared object, that can be linked into GNU m4 at run
* time. Information about creating modules is in ../modules/README.
*
* The current implementation uses either dlopen(3) (exists on
* GNU/Linux, OSF, Solaris, SunOS) or shl_load(3) (exists on HPUX). To
* enable this experimental feature give configure the `--with-modules'
* switch. This implementation is only tested on GNU/Linux, OSF,
* Solaris, SunOS and HPUX.
*
* A m4 module need only define one external symbol, called
* `m4_macro_table'. This symbol should point to a table of `struct
* builtin' exactly as the one in builtin.c. This table is pushed on a
* list of builtin tables and each definition therein is added to the
* symbol table.
*
* The code implementing loadable modules is modest. It is divided
* between the files builtin.c (user interface and support for multiple
* builtin tables) and this file (OS dependant routines).
*
* To load a module, use `loadmodule(modulename)'. The function
* `m4_loadmodule' calls module_load() in this file, which uses
* module_search() to find the module in the module search path. This
* path is initialised from the environment variable MP4HMODPATH, or if
* not set, initalised to a configuration time default. Module_search()
* constructs absolute file names and calls module_try_load(). This
* function reads the libtool .la file to get the real library name
* (which can be system dependent) and returns NULL on failure and a
* non-NULL void* on success. If succesful module_search() returns the
* value of this void*, which is a handle for the vm segment mapped.
* Module_load() checks to see if the module is alreay loaded, and if
* not, retrives the symbol `m4_macro_table' and returns it's value to
* m4_loadmodule(). This pointer should be a builtin*, which is
* installed using install_builtin_table().
*
* When a module is loaded, the function "void m4_init_module(struct
* obstack *obs)" is called, if defined. Any non NULL return value of
* this function will be the expansion of "loadmodule". Before program
* exit, all modules are unloaded and the function "void
* m4_finish_module(void)" is called, if defined.
*
* There is no way to unload a module unless at program exit. It is
* safe to load the same module several times, it has no effect.
**/
/* This list is used to check for repeated loading of the same modules. */
typedef struct module_list {
struct module_list *next;
char *modname;
void *handle;
} module_list;
static module_list *modules;
static char *add_suffix_searchdir __P ((const char *, const char *));
/*
* Initialisation. Currently the module search path in path.c is
* initialised from MP4HLIB. Only absolute path names are accepted to
* prevent the path search of the dlopen library from finding wrong
* files.
*/
void
module_init (void)
{
static int errors = MODULES_UNINITIALISED;
/* Do this only once! */
if (errors != MODULES_UNINITIALISED)
return;
errors = lt_dlinit();
if (errors == 0)
errors = lt_dladdsearchdir(".");
/* If the user set MP4HLIB, then use that as the start of libltdls
* module search path, else fall back on the default.
*/
if (errors == 0)
{
char *path = getenv("MP4HLIB");
if (path != NULL)
errors = lt_dladdsearchdir(path);
}
if (errors == 0)
errors = lt_dladdsearchdir(MP4HLIBDIR);
if (errors != 0)
{
/* Couldn't initialise the module system; diagnose and exit. */
const char *dlerror = lt_dlerror();
MP4HERROR ((EXIT_FAILURE, 0,
_("ERROR: failed to initialise modules: %s"), dlerror));
}
if (debug_level & DEBUG_TRACE_MODULES)
DEBUG_MESSAGE("Module system initialised.");
}
/*
* By default, when `foo/bar' is searched by libltdl (or in fact lt_dlopenext)
* it is only searched into ./foo and not other paths defined elsewhere.
* This is very sad, and instead of patching libtool, which may cause
* some headaches when upgrading, this function adds a given dir to a
* path list.
*/
static char *
add_suffix_searchdir (const char *oldpath, const char *dir)
{
const char *cp;
char *new_search_path, *sp, *next, *path;
int num, len, lendir, offset;
if (*dir == '/')
return xstrdup(dir);
lendir = strlen (dir);
/* First count paths */
cp = oldpath;
num = 1;
while (cp)
{
if (*cp == LT_PATHSEP_CHAR)
{
++num;
cp++;
}
else
cp = strchr (cp, LT_PATHSEP_CHAR);
}
new_search_path = xmalloc (strlen (oldpath) + num * (lendir + 1) + 2);
/* And copies directories */
path = xstrdup (oldpath);
sp = path;
offset = 0;
while (sp)
{
next = strchr (sp, LT_PATHSEP_CHAR);
if (next)
{
/* Special case: null path */
if (next == sp)
{
*(new_search_path+offset) = LT_PATHSEP_CHAR;
++offset;
sp = next + 1;
continue;
}
len = next - sp;
*next = '\0';
++next;
}
else
len = strlen (sp);
sprintf(new_search_path+offset, "%s/%s%c", sp, dir, LT_PATHSEP_CHAR);
offset += len + lendir + 2;
sp = next;
}
/* Remove trailing colon */
*(new_search_path+strlen (oldpath) + num * (lendir + 1)) = '\0';
xfree ((voidstar) path);
return new_search_path;
}
/*
* Load a dynamic library.
*/
void
library_load (const char *libname, struct obstack *obs)
{
lt_dlhandle library;
module_list *list;
char *dir;
const char *lib_name = libname;
const char *save_path = NULL;
char *cp, *new_search_path;
/* If it contains a slash, leading dir is appended to search path. */
cp = strrchr(libname, '/');
if (cp && cp != libname)
{
lib_name = cp + 1;
dir = xmalloc (cp - libname + 1);
strncpy (dir, libname, cp - libname);
dir[cp - libname] = '\0';
save_path = lt_dlgetsearchpath ();
if (NULL == save_path)
lt_dlsetsearchpath (dir);
else
{
save_path = xstrdup(save_path);
new_search_path = add_suffix_searchdir (save_path, dir);
lt_dlsetsearchpath (new_search_path);
xfree ((voidstar) new_search_path);
}
xfree ((voidstar) dir);
}
/* Dynamically load the named module. */
library = lt_dlopenext(lib_name);
if (library == NULL)
if (debug_level & DEBUG_TRACE_MODULES)
DEBUG_MESSAGE2("Error when looking into library `%s': %s",
libname, lt_dlerror());
if (save_path)
{
lt_dlsetsearchpath (save_path);
xfree ((voidstar) save_path);
}
if (library != NULL)
{
for (list = modules; list != NULL; list = list->next)
if (list->handle == library)
{
if (debug_level & DEBUG_TRACE_MODULES)
DEBUG_MESSAGE1("library `%s' handle already seen", libname);
lt_dlclose(library); /* close the duplicate copy */
return;
}
}
if (library != NULL)
{
if (debug_level & DEBUG_TRACE_MODULES)
{
const lt_dlinfo *libinfo = lt_dlgetinfo(library);
DEBUG_MESSAGE2("library `%s': loaded ok (=%s)",
libname, libinfo->filename);
}
}
else
{
/* Couldn't load the library; diagnose and exit. */
const char *dlerror = lt_dlerror();
if (dlerror == NULL)
MP4HERROR ((EXIT_FAILURE, 0,
_("ERROR: cannot find library: `%s'"), libname));
else
MP4HERROR ((EXIT_FAILURE, 0,
_("ERROR: cannot find library: `%s': %s"),
libname, dlerror));
}
}
void
module_load (const char *modname, struct obstack *obs)
{
module_init_t *init_func;
lt_dlhandle module;
module_list *list;
char *dir;
const char *mod_name = modname;
const char *save_path = NULL;
char *cp, *new_search_path;
builtin *bp;
/* If it contains a slash, leading dir is appended to search path. */
cp = strrchr(modname, '/');
if (cp && cp != modname)
{
mod_name = cp + 1;
dir = xmalloc (cp - modname + 1);
strncpy (dir, modname, cp - modname);
dir[cp - modname] = '\0';
save_path = lt_dlgetsearchpath ();
if (NULL == save_path)
lt_dlsetsearchpath (dir);
else
{
save_path = xstrdup(save_path);
new_search_path = add_suffix_searchdir (save_path, dir);
lt_dlsetsearchpath (new_search_path);
xfree ((voidstar) new_search_path);
}
xfree ((voidstar) dir);
}
/* Dynamically load the named module. */
module = lt_dlopenext(mod_name);
if (NULL == module)
if (debug_level & DEBUG_TRACE_MODULES)
DEBUG_MESSAGE2("Error when looking into module `%s': %s",
modname, lt_dlerror());
if (save_path)
{
lt_dlsetsearchpath (save_path);
xfree ((voidstar) save_path);
}
if (module != NULL)
{
for (list = modules; list != NULL; list = list->next)
if (list->handle == module)
{
if (debug_level & DEBUG_TRACE_MODULES)
DEBUG_MESSAGE1("module `%s' handle already seen", modname);
lt_dlclose(module); /* close the duplicate copy */
return;
}
}
/* Find the initialising table in the loaded module. */
if (module != NULL)
{
bp = (builtin*)lt_dlsym(module, "mp4h_macro_table");
if (bp == NULL)
{
if (debug_level & DEBUG_TRACE_MODULES)
DEBUG_MESSAGE1("module `%s': no symbol mp4h_macro_table", modname);
lt_dlclose(module);
module = NULL;
}
}
/* Find and run the initialising function in the loaded module
* (if any).
*/
if (module != NULL)
{
init_func = (module_init_t*)lt_dlsym(module, "mp4h_init_module");
if (init_func != NULL)
{
(*init_func)(obs);
}
else if (debug_level & DEBUG_TRACE_MODULES)
DEBUG_MESSAGE1("module `%s': no symbol mp4h_init_module", modname);
}
/* If the module was correctly loaded and has the necessary
* symbols, then update our internal tables to remember the
* new module.
*/
if (module != NULL)
{
if (debug_level & DEBUG_TRACE_MODULES)
{
const lt_dlinfo *modinfo = lt_dlgetinfo(module);
DEBUG_MESSAGE2("module %s: loaded ok (=%s)",
modname, modinfo->filename);
}
list = xmalloc (sizeof (struct module_list));
list->next = modules;
list->modname = xstrdup(modname);
list->handle = module;
modules = list;
install_builtin_table(bp);
}
else
{
/* Couldn't load the module; diagnose and exit. */
const char *dlerror = lt_dlerror();
if (dlerror == NULL)
MP4HERROR ((EXIT_FAILURE, 0,
_("ERROR: cannot find module: `%s'"), modname));
else
MP4HERROR ((EXIT_FAILURE, 0,
_("ERROR: cannot find module: `%s': %s"),
modname, dlerror));
}
}
void
module_unload_all(void)
{
int errors = 0;
struct module_list *next;
module_finish_t *finish_func;
/* Find and run the finishing function for each loaded module. */
while (modules != NULL)
{
finish_func = (module_finish_t*)
lt_dlsym(modules->handle, "mp4h_finish_module");
if (finish_func != NULL)
{
(*finish_func)();
if (debug_level & DEBUG_TRACE_MODULES)
DEBUG_MESSAGE1("module `%s' finish hook called", modules->modname);
}
errors = lt_dlclose (modules->handle);
if (errors != 0)
break;
if (debug_level & DEBUG_TRACE_MODULES)
DEBUG_MESSAGE1("module `%s' unloaded", modules->modname);
next = modules->next;
xfree ((voidstar) modules);
modules = next;
}
if (errors != 0)
errors = lt_dlexit();
if (errors != 0)
{
const char *dlerror = lt_dlerror();
if (modules == NULL)
{
if (dlerror == NULL)
MP4HERROR ((EXIT_FAILURE, 0,
_("ERROR: cannot close modules")));
else
MP4HERROR ((EXIT_FAILURE, 0,
_("ERROR: cannot cannot close modules: %s"),
dlerror));
}
else
{
if (dlerror == NULL)
MP4HERROR ((EXIT_FAILURE, 0,
_("ERROR: cannot close module: `%s'"),
modules->modname));
else
MP4HERROR ((EXIT_FAILURE, 0,
_("ERROR: cannot cannot close module: `%s': %s"),
modules->modname, dlerror));
}
}
}
#endif /* WITH_MODULES */