src/spbot/module.h
branchnew-lib-errors
changeset 217 7728d6ec3abf
parent 134 978041c1c04d
child 218 5229a5d098b2
--- /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