--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/spbot/module.h Wed May 27 23:57:48 2009 +0300
@@ -0,0 +1,295 @@
+#ifndef MODULE_H
+#define MODULE_H
+
+/**
+ * @file
+ *
+ * Dynamically loadable modules for use with nexus.
+ *
+ * The modules are loaded using dlopen(), and hence should be standard dynamic libraries. Modules are then "loaded" by
+ * resolving a `struct module_funcs` symbol called "<modname>_funcs", and then using the init func to construct a
+ * "context", which is then further manipulated by various other module functions.
+ *
+ * Modules are also reference counted, mainly for implementing module_unload(). When a module is first loaded, it has
+ * a reference count of one - the entry in struct modules. Later, modules can be referenced using module_get(), and
+ * returned with module_put() once done. The module should never be destroyed during its lifetime, until a
+ * module_unload() occurs. At this point, the module is removed from the modules_list, and the one reference is
+ * 'passed on' to the module itself. Once it has finished unloading, it can call module_unloaded() on the reference it was
+ * given by module_desc::unload, which may then result in a deferred module_destroy().
+ *
+ * Module destroying itself is an interesting issue, since modules effectively need to be able to destroy themselves
+ * (as they must be able to perform cleanup, and then notify completion from inside an event loop callback). This means
+ * that they cannot directly execute a module_destroy() on themselves - if we call dlclose() with dlopen-mapped code
+ * pages on the stack, a segfault ensues. Hence, they must call module_unloaded() on themselves, which then executes a
+ * deferred module_destroy() if there are no references left. Otherwise, the module should be safe from external code,
+ * as module_put() should never cause a module to be destroyed before module_unloaded() is executed, due to the primary
+ * reference.
+ *
+ * Ugh, it's compliated, I need to write a clearer explenation once it's implemented :)
+ */
+
+struct module;
+
+#include "nexus.h"
+#include "config.h"
+#include <lib/error.h>
+
+#include <sys/queue.h>
+#include <stdbool.h>
+
+enum module_error_code {
+ ERR_MODULE_NONE,
+ ERR_MODULE_NAME, ///< invalid module_info.name
+ ERR_MODULE_DUP, ///< module already opened
+ ERR_MODULE_PATH, ///< resolving the path failed
+ ERR_MODULE_OPEN, ///< dlopen() failed
+ ERR_MODULE_SYM, ///< invalid symbol
+ ERR_MODULE_INIT_FUNC, ///< invalid module_init_func_t
+ ERR_MODULE_CONF, ///< value error in configuration data
+ ERR_MODULE_STATE, ///< module in wrong state for operation
+};
+
+const struct error_list module_errors;
+
+/**
+ * Information required to load/identify a module.
+ */
+struct module_info {
+ /** Human-readable name */
+ const char *name;
+
+ /** Filesystem path to the .so */
+ const char *path;
+};
+
+/**
+ * A table of (function/other) pointers defining a module's behaviour. This is dynamically resolved from the module DSO
+ * using the "<mod_name>_module" symbol.
+ */
+struct module_desc {
+ /**
+ * Initialize the module, returning an opaque context pointer that is stored in the module state, and supplied for
+ * subsequent calls. The supplied nexus arg can be used to access the global state.
+ *
+ * Implementing this is mandatory.
+ *
+ * @param nexus a pointer to the nexus struct containing the global state
+ * @param ctx_ptr the context pointer should be returned via this
+ * @param err returned error info
+ */
+ err_t (*init) (struct nexus *nexus, void **ctx_ptr, error_t *err);
+
+ /**
+ * A module may define a set of available configuration parameters for use by module_conf, by setting this to an
+ * array of config_option's.
+ *
+ * The handler functions will recieve the module's context pointer as their ctx argument.
+ *
+ * Implementing this is optional, but recommended.
+ */
+ const struct config_option *config_options;
+
+ /**
+ * Unload the module, removing all handlers/callbacks added to the nexus' irc_client. This does not have to act
+ * immediately, and will still have access to all irc_* resources it had earlier, and may perform I/O to unload
+ * cleanly. But the unloading should complete reasonably quickly, after which all event handlers added by the
+ * module to the nexus' ev_base should have been removed, and resources released.
+ *
+ * The module given as an argument is the module itself - which has been removed from the modules_list - the
+ * primary reference is passed on to the module. Once the module has finished unloading, it may call module_put(),
+ * which may then call module_destroy(), if no other references were left.
+ *
+ * If the unload operation fails (returns an error code), then the module is considered as unloaded (as if
+ * module_unloaded() was called - don't call this if you return an error).
+ *
+ * Implementing this is optional, if all of this can be implemented in destroy.
+ *
+ * @param ctx the module's context pointer as returned by init
+ * @param module the hanging module reference, that must be passed to module_unloaded()
+ * @return error code
+ */
+ err_t (*unload) (void *ctx, struct module *module);
+
+ /**
+ * Destroy the module now. No later chances, the module's code will be unloaded directly after this, which means
+ * that attempts to execute the module's code (even on the stack...) after this will segfault.
+ *
+ * The module code /should/ garuntee that this is never called from *inside* the module code - calls to
+ * module_destroy() will be deferred via the event loop if needed.
+ *
+ * Implementing this is optional.
+ */
+ void (*destroy) (void *ctx);
+};
+
+/**
+ * A loaded module.
+ */
+struct module {
+ /** The identifying info for the module */
+ struct module_info info;
+
+ /** Possible dynamically allocated path */
+ char *path_buf;
+
+ /** The dlopen handle */
+ void *handle;
+
+ /** The module entry point */
+ struct module_desc *desc;
+
+ /** The module context object */
+ void *ctx;
+
+ /** Reference back to modules struct used to load this module */
+ struct modules *modules;
+
+ /** Reference count for destroy() */
+ size_t refcount;
+
+ /** Is the module currently being unloaded? */
+ bool unloading;
+
+ /** Our entry in the list of modules */
+ TAILQ_ENTRY(module) modules_list;
+};
+
+/**
+ * A set of loaded modules, and functionality to load more
+ */
+struct modules {
+ /** The nexus in use */
+ struct nexus *nexus;
+
+ /** Module search path */
+ const char *path;
+
+ /** List of loaded modules */
+ TAILQ_HEAD(module_ctx_modules, module) list;
+};
+
+
+/**
+ * Maximum length of a module name
+ */
+#define MODULE_NAME_MAX 24
+
+/**
+ * Maximum length of module symbol suffix
+ */
+#define MODULE_SUFFIX_MAX 16
+
+/**
+ * Maximum length of symbol name name, including terminating NUL
+ */
+#define MODULE_SYMBOL_MAX (MODULE_NAME_MAX + 1 + MODULE_SUFFIX_MAX + 1)
+
+/**
+ * Create a new modules state
+ */
+err_t modules_create (struct modules **modules_ptr, struct nexus *nexus);
+
+/**
+ * Set a search path for finding modules by name. The given string won't be copied.
+ *
+ * A module called "<name>" will be searched for at "<path>/mod_<name>.so"
+ *
+ * If path is NULL, this doesn't change anything. This returns the old path, which may be NULL.
+ *
+ * @param modules the modules state
+ * @param path the new search path, or NULL to just get the old one
+ * @return the old search path
+ */
+const char* modules_path (struct modules *modules, const char *path);
+
+/**
+ * Get a reference to the module, which must be returned using module_put
+ *
+ * @param modules the modules state
+ * @param name the module name to get
+ * @return the module struct, or NULL if not found
+ */
+struct module* modules_get (struct modules *modules, const char *name);
+
+/**
+ * Unload all modules, this just calls module_unload for each module, logging errors as warnings.
+ */
+err_t modules_unload (struct modules *modules);
+
+/**
+ * Destroy all modules immediately.
+ */
+void modules_destroy (struct modules *modules);
+
+
+
+/*******************************************/
+
+
+/**
+ * Return a module's name
+ */
+const char* module_name (struct module *module);
+
+/**
+ * Load a new module, as named by info.
+ *
+ * If info->path is not given, the module will be searched for using the path set by modules_path().
+ *
+ * If module_ptr is given, a reference (that must be module_put'd) is returned, that must be returned using
+ * module_put().
+ *
+ * @param modules the module-loading context
+ * @param module_ptr retuturned new module struct, as a new reference, if not NULL.
+ * @param info the info required to identify and load the module
+ * @param err returned error info
+ */
+err_t module_load (struct modules *modules, struct module **module_ptr, const struct module_info *info, error_t *err);
+
+/**
+ * Return a module retrieved using module_get
+ */
+void module_put (struct module *module);
+
+/**
+ * Look up a module configuration option by name
+ */
+const struct config_option* module_conf_lookup (struct module *module, const char *name, error_t *err);
+
+/**
+ * Apply a module configuration option using a structured value
+ */
+err_t module_conf (struct module *module, const struct config_option *opt, const struct config_value *value, error_t *err);
+
+/**
+ * Set a module configuration option using a raw value
+ */
+err_t module_conf_raw (struct module *module, const char *name, char *value, error_t *err);
+
+/**
+ * Unload a module. This removes the module from the modules_list, marks it as unloading, and then calls the module's
+ * \p unload function, passing it the primary reference. The module's unload code will then go about shutting down the
+ * module, and once that is done, it may module_put() the primary reference, which may then lead to module_destroy().
+ *
+ * This returns ERR_MODULE_STATE if the module is already being unloaded, or other errors from the module's own unload
+ * functionality.
+ */
+err_t module_unload (struct module *module);
+
+/**
+ * Used by a module itself to indicate that an module_desc::unload() operation has completed. This will execute a
+ * deferred module_destroy() if there are no more references left on the module.
+ *
+ * Note: this is not intended to be called from outside the given module itself...
+ */
+void module_unloaded (struct module *module);
+
+/**
+ * Destroy a module, releasing as many resources as possible, but not stopping for errors.
+ *
+ * This does not enforce the correct refcount - 'tis the caller's responsibility. Prints out a warning if
+ * refcount > 0.
+ */
+void module_destroy (struct module *module);
+
+#endif