blob: c00b8aa1893a9998dcf2bf46a1cc601418e8503c [file] [log] [blame]
/* GNU m4 -- A simple macro processor
Copyright (C) 1989-1994, 1998-1999, 2002-2008, 2010, 2013-2014, 2017
Free Software Foundation, Inc.
This file is part of GNU M4.
GNU M4 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 3 of the License, or
(at your option) any later version.
GNU M4 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, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <dlfcn.h>
#include "m4private.h"
#include "xvasprintf.h"
/* Define this to see runtime debug info. Implied by DEBUG. */
/*#define DEBUG_MODULES */
/*
* This file implements dynamic modules in GNU M4. A module is a
* compiled shared object, that can be loaded into GNU M4 at run
* time. Information about creating modules is in ../modules/README.
*
* This implementation uses libltdl, which is in turn can open modules
* using either dlopen(3) (exists on GNU/Linux, OSF, Solaris, SunOS and
* others), shl_load(3) (exists on HPUX), LoadLibrary(3) (exists on
* Windows, cygwin, OS/2), load_add_on(3) (exists on BeOS), NSAddImage
* (exists on MacOS) and can also fall back to dld_link(3) from GNU
* libdld or lt_dlpreload from libtool if shared libraries are not
* available on the host machine.
*
* An M4 module will usually define an external symbol named after the
* basename of the loadable module:
*
* void
* mymod_LTX_m4_init_module (m4 *context, m4_module *module,
* m4_obstack *obs)
*
* The function is only called the first time the module is included
* and generally uses either `m4_install_builtins' or
* `m4_install_macros' (or both!) to register whatever builtins and
* macros are provided by the module.
*
* To load a module, call m4_module_load(), which searches for the
* module in directories from M4PATH. The search path is initialized
* from the environment variable M4PATH, followed by the configuration
* time default where the modules shipped with M4 itself are installed.
* `m4_module_load' returns NULL on failure, or else an opaque module
* handle for the newly mapped vm segment containing the module code.
* If the module is not already loaded, m4_module_load() the builtins
* and macros registered by `mymod_LTX_m4_init_module' are installed
* into the symbol table using `install_builtin_table' and `install_
* macro_table' respectively.
**/
#define MODULE_SELF_NAME "!myself!"
#if DLSYM_USCORE
static void *
uscore_sym (void *handle, const char *symbol)
{
char *symname = xasprintf ("_%s", symbol);
void *address = dlsym (handle, symname);
free (symname);
return address;
}
#define dlsym uscore_sym
#endif
static const char * module_dlerror (void);
static void install_builtin_table (m4*, m4_module *);
static void install_macro_table (m4*, m4_module *);
static int compare_builtin_CB (const void *a, const void *b);
const char *
m4_get_module_name (const m4_module *module)
{
assert (module);
return module->name;
}
void *
m4_module_import (m4 *context, const char *module_name,
const char *symbol_name, m4_obstack *obs)
{
m4_module * module = m4__module_find (context, module_name);
void * symbol_address = NULL;
/* Try to load the module if it is not yet available (errors are
diagnosed by m4_module_load). */
/* FIXME - should this use m4__module_open instead, to avoid
polluting the symbol table when importing a function? */
if (!module)
module = m4_module_load (context, module_name, obs);
if (module)
{
symbol_address = dlsym (module->handle, symbol_name);
if (!symbol_address)
m4_error (context, 0, 0, NULL,
_("cannot load symbol `%s' from module `%s'"),
symbol_name, module_name);
}
return symbol_address;
}
void
m4_install_builtins (m4 *context, m4_module *module, const m4_builtin *bp)
{
assert (context);
assert (module);
assert (bp);
const m4_builtin *tmp;
m4__builtin *builtin;
for (tmp = bp; tmp->name; tmp++)
module->builtins_len++;
module->builtins = (m4__builtin *) xnmalloc (module->builtins_len,
sizeof *module->builtins);
for (builtin = module->builtins; bp->name != NULL; bp++, builtin++)
{
/* Sanity check that builtins meet the required interface. */
assert (bp->min_args <= bp->max_args);
assert (bp->min_args > 0 ||
(bp->flags & (M4_BUILTIN_BLIND|M4_BUILTIN_SIDE_EFFECT)) == 0);
assert (bp->max_args ||
(bp->flags & M4_BUILTIN_FLATTEN_ARGS) == 0);
assert ((bp->flags & ~M4_BUILTIN_FLAGS_MASK) == 0);
memcpy (&builtin->builtin, bp, sizeof *bp);
builtin->builtin.name = xstrdup (bp->name);
builtin->module = module;
}
qsort (module->builtins, module->builtins_len,
sizeof *module->builtins, compare_builtin_CB);
}
static void
install_builtin_table (m4 *context, m4_module *module)
{
size_t i;
assert (context);
assert (module);
for (i = 0; i < module->builtins_len; i++)
{
m4_symbol_value *value = m4_symbol_value_create ();
const char *name = module->builtins[i].builtin.name;
m4__set_symbol_value_builtin (value, &module->builtins[i]);
if (m4_get_prefix_builtins_opt (context))
name = xasprintf ("m4_%s", name);
m4_symbol_pushdef (M4SYMTAB, name, strlen (name), value);
if (m4_get_prefix_builtins_opt (context))
DELETE (name);
}
if (i)
m4_debug_message (context, M4_DEBUG_TRACE_MODULE,
_("module %s: builtins loaded"),
m4_get_module_name (module));
}
void
m4_install_macros (m4 *context, m4_module *module, const m4_macro *mp)
{
assert (context);
assert (module);
assert (mp);
module->macros = (m4_macro *) mp;
}
static void
install_macro_table (m4 *context, m4_module *module)
{
const m4_macro *mp;
assert (context);
assert (module);
mp = module->macros;
if (mp)
{
for (; mp->name != NULL; mp++)
{
m4_symbol_value *value = m4_symbol_value_create ();
size_t len = strlen (mp->value);
/* Sanity check that builtins meet the required interface. */
assert (mp->min_args <= mp->max_args);
m4_set_symbol_value_text (value, xmemdup0 (mp->value, len), len, 0);
VALUE_MODULE (value) = module;
VALUE_MIN_ARGS (value) = mp->min_args;
VALUE_MAX_ARGS (value) = mp->max_args;
m4_symbol_pushdef (M4SYMTAB, mp->name, strlen (mp->name), value);
}
m4_debug_message (context, M4_DEBUG_TRACE_MODULE,
_("module %s: macros loaded"),
m4_get_module_name (module));
}
}
m4_module *
m4_module_load (m4 *context, const char *name, m4_obstack *obs)
{
m4_module *module = m4__module_find (context, name);
if (!module)
{
module = m4__module_open (context, name, obs);
if (module)
{
install_builtin_table (context, module);
install_macro_table (context, module);
}
}
return module;
}
/* Return successive loaded modules. */
m4_module *
m4_module_next (m4 *context, m4_module *module)
{
return module ? module->next : context->modules;
}
/* Return the first loaded module that passes the registered interface test
and is called NAME. */
m4_module *
m4__module_find (m4 *context, const char *name)
{
m4_module **pmodule = (m4_module **) m4_hash_lookup (context->namemap, name);
return pmodule ? *pmodule : NULL;
}
/* Compare two builtins A and B for sorting, as in qsort. */
static int
compare_builtin_CB (const void *a, const void *b)
{
const m4__builtin *builtin_a = (const m4__builtin *) a;
const m4__builtin *builtin_b = (const m4__builtin *) b;
int result = strcmp (builtin_a->builtin.name, builtin_b->builtin.name);
/* A builtin module should never provide two builtins with the same
name. */
assert (result || a == b);
return result;
}
/* Load a module. NAME can be a absolute file name or, if relative,
it is searched for in the module path. The module is unloaded in
case of error. */
m4_module *
m4__module_open (m4 *context, const char *name, m4_obstack *obs)
{
static const char * suffixes[] = { "", LT_MODULE_EXT, NULL };
m4_module * module = NULL;
assert (context);
char *filepath = m4_path_search (context, name, suffixes);
void *handle = NULL;
if (filepath)
{
handle = dlopen (filepath, RTLD_NOW|RTLD_GLOBAL);
free (filepath);
}
if (handle)
{
m4_debug_message (context, M4_DEBUG_TRACE_MODULE,
_("module %s: opening file %s"),
name ? name : MODULE_SELF_NAME,
quotearg_style (locale_quoting_style, name));
module = (m4_module *) xzalloc (sizeof *module);
module->name = xstrdup (name);
module->handle = handle;
module->next = context->modules;
context->modules = module;
m4_hash_insert (context->namemap, xstrdup (name), module);
/* Find and run any initializing function in the opened module,
the first time the module is opened. */
char *entry_point = xasprintf ("include_%s", name);
m4_module_init_func *init_func =
(m4_module_init_func *) dlsym (handle, entry_point);
free (entry_point);
if (init_func)
{
init_func (context, module, obs);
m4_debug_message (context, M4_DEBUG_TRACE_MODULE,
_("module %s: init hook called"), name);
}
else
{
m4_error (context, EXIT_FAILURE, 0, NULL,
_("module `%s' has no entry point"), name);
}
m4_debug_message (context, M4_DEBUG_TRACE_MODULE,
_("module %s: opened"), name);
}
else
{
const char *err = dlerror ();
if (!err) err = _("unknown error");
/* Couldn't open the module; diagnose and exit. */
m4_error (context, EXIT_FAILURE, 0, NULL,
_("cannot open module `%s': %s"), name, err);
}
return module;
}