implement modules_path + module_load with NULL path
authorTero Marttila <terom@fixme.fi>
Wed, 01 Apr 2009 18:30:47 +0300
changeset 108 50ff7ac8a725
parent 107 5c1eeb45c7f2
child 109 bfe9b9a8fe5b
implement modules_path + module_load with NULL path
TODO
src/error.c
src/error.h
src/lua_objs.c
src/module.c
src/module.h
--- a/TODO	Wed Apr 01 17:29:29 2009 +0300
+++ b/TODO	Wed Apr 01 18:30:47 2009 +0300
@@ -11,10 +11,9 @@
  * reconnect, maybe cycling servers?
 
 config:
- * A more advanced structured value parser that can then handle all the various configuration values sanely
+ * An even more advanced structured value parser that can then handle config values for more things like the command line --options
 
 modules:
- * Automatic module-name -> path mapping, and autoloading of modules somehow
  * proper unload support, there needs to be some kind of completion notification thing that can then destroy the module
    once it's completely unloaded - maybe some kind of module-resource-reference-counting thing, which will eventually
    be needed anyways?
--- a/src/error.c	Wed Apr 01 17:29:29 2009 +0300
+++ b/src/error.c	Wed Apr 01 18:30:47 2009 +0300
@@ -13,6 +13,7 @@
     {   ERR_CALLOC,                         "calloc",                                   ERR_EXTRA_NONE      },
     {   ERR_STRDUP,                         "strdup",                                   ERR_EXTRA_NONE      },
     {   ERR_SIGACTION,                      "sigaction",                                ERR_EXTRA_ERRNO     },
+    {   ERR_ACCESS_READ,                    "access(R_OK)",                             ERR_EXTRA_ERRNO     },
     {   ERR_GETADDRINFO,                    "getaddrinfo",                              ERR_EXTRA_GAI       },
     {   ERR_GETADDRINFO_EMPTY,              "getaddrinfo: no results",                  ERR_EXTRA_NONE      },
     {   ERR_EVENT_NEW,                      "event_new",                                ERR_EXTRA_NONE      },
@@ -70,8 +71,11 @@
     {   _ERR_INVALID,                       NULL,                                       0                   }
 
 }, _module_error_desc[] = {
+    {   ERR_MODULE_NAME,                    "invalid module name",                      ERR_EXTRA_NONE      },
+    {   ERR_MODULE_DUP,                     "module already loaded",                    ERR_EXTRA_NONE      },
+    {   ERR_MODULE_PATH,                    "invalid module path",                      ERR_EXTRA_STR       },
     {   ERR_MODULE_OPEN,                    "module dlopen() failed",                   ERR_EXTRA_STR       },
-    {   ERR_MODULE_NAME,                    "invalid module name",                      ERR_EXTRA_NONE      },
+    {   ERR_MODULE_SYM,                     "module dlsym() failed",                    ERR_EXTRA_STR       },
     {   ERR_MODULE_INIT_FUNC,               "invalid module init func",                 ERR_EXTRA_STR       },
     {   ERR_MODULE_CONF,                    "module_conf",                              ERR_EXTRA_STR       },
     {   _ERR_INVALID,                       NULL,                                       0                   }
--- a/src/error.h	Wed Apr 01 17:29:29 2009 +0300
+++ b/src/error.h	Wed Apr 01 18:30:47 2009 +0300
@@ -45,6 +45,7 @@
     ERR_CALLOC,
     ERR_STRDUP,
     ERR_SIGACTION,
+    ERR_ACCESS_READ,
     
     /** DNS resolver */
     _ERR_RESOLVER   = 0x000200,
--- a/src/lua_objs.c	Wed Apr 01 17:29:29 2009 +0300
+++ b/src/lua_objs.c	Wed Apr 01 18:30:47 2009 +0300
@@ -1,6 +1,7 @@
 #include "lua_objs.h"
 #include "log.h"
 
+#include <stdlib.h>
 #include <string.h>
 
 #include <lua5.1/lua.h>
@@ -473,8 +474,43 @@
  */
 struct lua_modules {
     struct modules *modules;
+    
+    // strdup'd path for module_path
+    // XXX: remove when gc'd
+    char *path;
 };
 
+static int lua_modules_path (lua_State *L)
+{
+    struct lua_modules *lua_modules = lua_obj_get_obj(L, __func__, "spbot.modules");
+    char *path = NULL;
+    const char *old_path;
+    
+    if (!lua_isnoneornil(L, 2)) {
+        // the new path
+        if ((path = strdup(luaL_checkstring(L, 2))) == NULL)
+            return luaL_error(L, "strdup");
+    }
+
+    // set or get
+    old_path = modules_path(lua_modules->modules, path);
+
+    // return the old path
+    if (old_path)
+        lua_pushstring(L, old_path);
+    else
+        lua_pushnil(L);
+
+    if (path) {
+        // replace the old path
+        free(lua_modules->path); 
+        lua_modules->path = path;
+    }
+
+    // ok
+    return 1;
+}
+
 static int lua_modules_load (lua_State *L)
 {
     struct lua_modules *lua_modules = lua_obj_get_obj(L, __func__, "spbot.modules");
@@ -484,7 +520,7 @@
 
     // the module name/path
     info.name = luaL_checkstring(L, 2);
-    info.path = luaL_checkstring(L, 3);
+    info.path = lua_isnoneornil(L, 3) ? NULL : luaL_checkstring(L, 3);
 
     // load
     if (module_load(lua_modules->modules, &module, &info, &err))
@@ -511,6 +547,7 @@
 }
 
 static const struct luaL_Reg lua_modules_methods[] = {
+    {   "path",         &lua_modules_path           },
     {   "load",         &lua_modules_load           },
     {   "module",       &lua_modules_module         },
     {   NULL,           NULL                        }
--- a/src/module.c	Wed Apr 01 17:29:29 2009 +0300
+++ b/src/module.c	Wed Apr 01 18:30:47 2009 +0300
@@ -2,6 +2,7 @@
 #include "log.h"
 
 #include <stdlib.h>
+#include <unistd.h>
 #include <dlfcn.h>
 #include <string.h>
 #include <assert.h>
@@ -26,6 +27,17 @@
     return SUCCESS;
 }
 
+const char* modules_path (struct modules *modules, const char *path)
+{
+    const char *old_path = modules->path;
+
+    if (path)
+        // replace
+        modules->path = path;
+
+    return old_path;
+}
+
 const char* module_name (struct module *module)
 {
     return module->info.name;
@@ -53,27 +65,105 @@
     return SUCCESS;
 }
 
-err_t module_load (struct modules *modules, struct module **module_ptr, const struct module_info *info, struct error_info *err)
+/**
+ * XXX: ugly function to format to a dynamically allocated string
+ */
+char *strfmt (const char *fmt, ...)
+{
+    va_list vargs;
+    size_t len;
+    char *buf;
+
+    // figure out the length of the resulting string
+    va_start(vargs, fmt);
+
+    len = vsnprintf(NULL, 0, fmt, vargs) + 1;
+
+    va_end(vargs);
+
+    // malloc
+    if ((buf = malloc(len)) == NULL)
+        return NULL;
+
+    // format
+    va_start(vargs, fmt);
+
+    vsnprintf(buf, len, fmt, vargs);
+
+    va_end(vargs);
+
+    // ok
+    return buf;
+}
+
+/**
+ * Looks up a module's path by name, returning a dynamically allocated string with the path on success
+ */
+static char* module_find (struct modules *modules, struct module_info *info, struct error_info *err)
+{
+    char *path = NULL;
+
+    // build the path...
+    if ((path = strfmt("%s/mod_%s.so", modules->path, info->name)) == NULL)
+        JUMP_SET_ERROR(err, ERR_STRDUP);
+
+    // exists and readable?
+    if (access(path, R_OK))
+        // XXX: this doesn't contain the path...
+        JUMP_SET_ERROR_STR(err, ERR_MODULE_PATH, "module not found in search path");
+    
+    // ok
+    return path;
+
+error:
+    // release the dynamically allocated path
+    free(path);
+
+    return NULL;
+}
+
+err_t module_load (struct modules *modules, struct module **module_ptr, const struct module_info *_info, struct error_info *err)
 {
     struct module *module;
 
     // validate the module name
-    if (strlen(info->name) > MODULE_NAME_MAX)
+    if (strlen(_info->name) > MODULE_NAME_MAX)
         return SET_ERROR(err, ERR_MODULE_NAME);
     
+    // already open with the same name?
+    if (module_get(modules, _info->name))
+        return SET_ERROR(err, ERR_MODULE_DUP);
+
     // alloc
     if ((module = calloc(1, sizeof(*module))) == NULL)
         return SET_ERROR(err, ERR_CALLOC);
     
     // store
-    module->info = *info;
+    module->info = *_info;
     module->modules = modules;
+ 
+    // add to modules list
+    TAILQ_INSERT_TAIL(&modules->list, module, modules_list);
+   
+    // lookup path?
+    if (!module->info.path) {
+        // have a search path?
+        if (!modules->path)
+            JUMP_SET_ERROR_STR(err, ERR_MODULE_PATH, "no module search path defined");
+
+        // try and resolve the path automatically
+        if ((module->path_buf = module_find(modules, &module->info, err)) == NULL)
+            goto error;
+        
+        // use that path
+        module->info.path = module->path_buf;
+    }
 
     // clear dlerrors
     (void) dlerror();
 
     // load it
-    if ((module->handle = dlopen(info->path, RTLD_NOW)) == NULL)
+    if ((module->handle = dlopen(module->info.path, RTLD_NOW)) == NULL)
         JUMP_SET_ERROR_STR(err, ERR_MODULE_OPEN, dlerror());
     
     // load the funcs symbol
@@ -84,9 +174,6 @@
     if ((module->desc->init(modules->nexus, &module->ctx, err)))
         goto error;
 
-    // add to modules list
-    TAILQ_INSERT_TAIL(&modules->list, module, modules_list);
-
     // ok
     if (module_ptr)
         *module_ptr = module;
@@ -94,8 +181,8 @@
     return SUCCESS;
 
 error:
-    // XXX: cleanup
-    free(module);
+    // cleanup
+    module_destroy(module);
 
     return ERROR_CODE(err);    
 }
@@ -172,9 +259,12 @@
     TAILQ_REMOVE(&module->modules->list, module, modules_list);
 
     // unload the dl handle
-    if (dlclose(module->handle))
+    if (module->handle && dlclose(module->handle))
         log_error("dlclose(%s): %s", module->info.name, dlerror());
     
+    // release the path buf, if any
+    free(module->path_buf);
+
     // free the module info
     free(module);
 }
--- a/src/module.h	Wed Apr 01 17:29:29 2009 +0300
+++ b/src/module.h	Wed Apr 01 18:30:47 2009 +0300
@@ -69,6 +69,9 @@
     /** The identifying info for the module */
     struct module_info info;
 
+    /** Possible dynamically allocated path */
+    char *path_buf;
+
     /** The dlopen handle */
     void *handle;
 
@@ -97,6 +100,9 @@
     /** The nexus in use */
     struct nexus *nexus;
 
+    /** Module search path */
+    const char *path;
+
     /** List of loaded modules */
     TAILQ_HEAD(module_ctx_modules, module) list;
 };
@@ -107,8 +113,10 @@
 enum module_error_code {
     _ERR_MODULE_BEGIN = _ERR_MODULE,
     
+    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_NAME,        ///< invalid module_info.name
     ERR_MODULE_SYM,         ///< invalid symbol
     ERR_MODULE_INIT_FUNC,   ///< invalid module_init_func_t
     ERR_MODULE_CONF,        ///< value error in configuration data
@@ -136,12 +144,27 @@
 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);
+
+/**
  * Return a module's name
  */
 const char* module_name (struct module *module);
 
 /**
- * Load a new 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().
  *
  * @param modules the module-loading context
  * @param module_ptr return the new module via this, if not NULL