Autoconf, Automake, and Libtool: Unloading a Module

| ### 20.1.4 Unloading a Module When unloading a module, several things must be done: Any built-in commands implemented by this module must be unregistered so that Sic doesn't try to call them after the implementation has been removed. Any syntax extensions implemented by this module must be similarly unregistered, including `syntax_init` and `syntax_finish` functions. If there is a finalisation entry point in the module, ``module_finish'` (see section 20.1.3 Loading a Module), it must be called. My first cut implementation of a module subsystem kept a list of the entry points associated with each module so that they could be looked up and removed when the module was subsequently unloaded. It also kept track of multiply loaded modules so that a module wasn't unloaded prematurely. libltdl already does all of this though, and it is wasteful to duplicate all of that work. This system uses `lt_dlforeach` and `lt_dlgetinfo` to access libltdls records of loaded modules, and save on duplication. These two functions are described fully insection `Libltdl interface' in The Libtool Manual.
    static int unload_ltmodule (lt_dlhandle module, lt_ptr_t data);

    struct unload_data { Sic *sic; const char *name; };

    int
    module_unload (Sic *sic, const char *name)
    {
      struct unload_data data;

      last_error = NULL;

      data.sic = sic;
      data.name = name;

      /* Stopping might be an error, or we may have unloaded the module. */
      if (lt_dlforeach (unload_ltmodule, (lt_ptr_t) &data) != 0)
        if (!last_error)
          return SIC_OKAY;

      if (!last_error)
        last_error = module_not_found_error;

      return SIC_ERROR;
    }
This function asks libltdl to call the function `unload_ltmodule` for each of the modules it has loaded, along with some details of the module it wants to unload. The tricky part of the callback function below is recalculating the entry point addresses for the module to be unloaded and then removing all matching addresses from the appropriate internal structures. Otherwise, the balance of this callback is involved in informing the calling `lt_dlforeach` loop of whether a matching module has been found and handled:
    static int userdata_address_compare (List *elt, void *match);

    /* This callback returns 0 if the module was not yet found.
       If there is an error, LAST_ERROR will be set, otherwise the
       module was successfully unloaded. */
    static int
    unload_ltmodule (lt_dlhandle module, void *data)
    {
      struct unload_data *unload = (struct unload_data *) data;
      const lt_dlinfo *module_info = lt_dlgetinfo (module);

      if ((unload == NULL)
          || (unload->name == NULL)
          || (module_info == NULL)
          || (module_info->name == NULL)
          || (strcmp (module_info->name, unload->name) != 0))
        {
          /* No match, return 0 to keep searching */
          return 0;
        }

      if (module)
        {
          /* Fetch the addresses of the entrypoints into the module. */
          Builtin *builtin_table
            = (Builtin*) lt_dlsym (module, "builtin_table");
          Syntax *syntax_table
            = (Syntax *) lt_dlsym (module, "syntax_table");
          void *syntax_init_address
            = (void *) lt_dlsym (module, "syntax_init");
          void **syntax_finish_address
            = (void *) lt_dlsym (module, "syntax_finish");
          List *stale;

          /* Remove all references to these entry points in the internal
             data structures, before actually unloading the module. */
          stale = list_remove (&unload->sic->syntax_init,
                       syntax_init_address, userdata_address_compare);
          XFREE (stale);

          stale = list_remove (&unload->sic->syntax_finish,
                       syntax_finish_address, userdata_address_compare);
          XFREE (stale);

          if (builtin_table
              && builtin_remove (unload->sic, builtin_table) != SIC_OKAY)
            {
              last_error = builtin_unload_error;
              module = NULL;
            }

          if (syntax_table
              && SIC_OKAY != syntax_remove (unload->sic, module,
                                            syntax_table))
            {
              last_error = syntax_unload_error;
              module = NULL;
            }
        }

      if (module)
        {
          ModuleFinish *finish_func
            = (ModuleFinish *) lt_dlsym (module, "module_finish");

          if (finish_func)
            (*finish_func) (unload->sic);
        }

      if (module)
        {
          if (lt_dlclose (module) != 0)
            module = NULL;
        }

      /* No errors?  Stop the search! */
      if (module)
        return 1;

      /* Find a suitable diagnostic. */
      if (!last_error)
        last_error = lt_dlerror();
      if (!last_error)
        last_error = module_not_unloaded_error;

      /* Error diagnosed.  Stop the search! */
      return -1;
    }

    static int
    userdata_address_compare (List *elt, void *match)
    {
      return (int) (elt->userdata - match);
    }
The `userdata_address_compare` helper function at the end is used to compare the address of recalculated entry points against the already registered functions and handlers to find which items need to be unregistered. There is also a matching header file to export the module interface, so that the code for loadable modules can make use of it:
    #ifndef SIC_MODULE_H
    #define SIC_MODULE_H 1

    #include <sic/builtin.h>
    #include <sic/common.h>
    #include <sic/sic.h>

    BEGIN_C_DECLS

    typedef void ModuleInit         (Sic *sic);
    typedef void ModuleFinish       (Sic *sic);

    extern const char *module_error (void);
    extern int module_init          (void);
    extern int module_load          (Sic *sic, const char *name);
    extern int module_unload        (Sic *sic, const char *name);

    END_C_DECLS

    #endif /* !SIC_MODULE_H */
This header also includes some of the other Sic headers, so that in most cases, the source code for a module need only ``#include <sic/module.h>'`. To make the module loading interface useful, I have added built-ins for ``load'` and ``unload'`. Naturally, these must be compiled into the bare `sic` executable, so that it is able to load additional modules:
    #if HAVE_CONFIG_H
    #  include <config.h>
    #endif

    #include "module.h"
    #include "sic_repl.h"

    /* List of built in functions. */
    #define builtin_functions               \
            BUILTIN(exit,           0, 1)   \
            BUILTIN(load,           1, 1)   \
            BUILTIN(unload,         1, -1)

    BUILTIN_DECLARATION (load)
    {
      int status = SIC_ERROR;

      if (module_load (sic, argv[1]) < 0)
        {
          sic_result_clear (sic);
          sic_result_append (sic, "module \"", argv[1], "\" not loaded: ",
                             module_error (), NULL);
        }
      else
        status = SIC_OKAY;

      return status;
    }

    BUILTIN_DECLARATION (unload)
    {
      int status = SIC_ERROR;
      int i;

      for (i = 1; argv[i]; ++i)
        if (module_unload (sic, argv[i]) != SIC_OKAY)
          {
            sic_result_clear (sic);
            sic_result_append (sic, "module \"", argv[1],
                               "\" not unloaded: ", module_error (), NULL);
          }
        else
          status = SIC_OKAY;

      return status;
    }
These new built-in commands are simply wrappers around the module loading code in ``module.c'`. As with ``dlopen'`, you can use libltdl to ``lt_dlopen'` the main executable, and then lookup its symbols. I have simplified the initialisation of Sic by replacing the `sic_init` function in ``src/sic.c'` by `loading' the executable itself as a module. This works because I was careful to use the same format in ``sic_builtin.c'` and ``sic_syntax.c'` as would be required for a genuine loadable module, like so:
      /* initialise the module subsystem */
      if (module_init () != SIC_OKAY)
          sic_fatal ("module initialisation failed");

      if (module_load (sic, NULL) != SIC_OKAY)
          sic_fatal ("sic initialisation failed");

This document was generated by Gary V. Vaughan on February, 8 2006 using texi2html