From 59fdb03ee0bc3dedb5306fa87c07e1aa8708fccf Mon Sep 17 00:00:00 2001 From: Thomas Porzelt Date: Wed, 22 Oct 2008 16:54:44 +0200 Subject: [PATCH] implement loading of modules --- src/lighttpd.c | 5 +- src/module.c | 153 ++++++++++++++++++++++++++++++++++++++++++++++ src/module.h | 20 +++--- src/plugin.c | 23 ++++--- src/plugin.h | 5 +- src/plugin_core.c | 53 ++++++++++++++++ src/server.c | 17 +++--- src/server.h | 4 ++ src/wscript | 5 +- wscript | 6 +- 10 files changed, 258 insertions(+), 33 deletions(-) create mode 100644 src/module.c diff --git a/src/lighttpd.c b/src/lighttpd.c index 02c4355..f5f538c 100644 --- a/src/lighttpd.c +++ b/src/lighttpd.c @@ -17,6 +17,7 @@ int main(int argc, char *argv[]) { gboolean free_config_path = TRUE; gchar *config_path = NULL; + gchar *module_dir = NULL; gboolean luaconfig = FALSE; gboolean test_config = FALSE; gboolean show_version = FALSE; @@ -27,6 +28,7 @@ int main(int argc, char *argv[]) { { "config", 'c', 0, G_OPTION_ARG_FILENAME, &config_path, "filename/path of the config", "PATH" }, { "lua", 'l', 0, G_OPTION_ARG_NONE, &luaconfig, "use the lua config frontend", NULL }, { "test", 't', 0, G_OPTION_ARG_NONE, &test_config, "test config and exit", NULL }, + { "module-dir", 'm', 0, G_OPTION_ARG_STRING, &module_dir, "module directory", NULL }, { "version", 'v', 0, G_OPTION_ARG_NONE, &show_version, "show version and exit", NULL }, { NULL, 0, 0, 0, NULL, NULL, NULL } }; @@ -67,7 +69,8 @@ int main(int argc, char *argv[]) { srv = server_new(); - plugin_register(srv, "core", plugin_core_init); + srv->module_dir = module_dir; + srv->core_plugin = plugin_register(srv, "core", plugin_core_init); /* if no path is specified for the config, read lighttpd.conf from current directory */ if (config_path == NULL) { diff --git a/src/module.c b/src/module.c new file mode 100644 index 0000000..20201b8 --- /dev/null +++ b/src/module.c @@ -0,0 +1,153 @@ +#include "base.h" +#include "module.h" + + +modules *modules_init(gpointer main) { + modules *m = g_slice_new(modules); + + m->version = MODULE_VERSION; + m->main = main; + m->mods = g_array_new(FALSE, TRUE, sizeof(module*)); + + return m; +} + +/* for internal use only */ +static module *module_lookup(modules *mods, const gchar *name) { + module *mod; + GArray *a = mods->mods; + + for (guint i = 0; i < a->len; i++) { + mod = g_array_index(a, module*, i); + if (mod != NULL && g_str_equal(mod->name->str, name)) + return mod; + } + + return NULL; +} + +void modules_cleanup(server *srv) { + /* unload all modules */ + GArray *a = srv->modules->mods; + module *mod; + + for (guint i = 0; i < a->len; i++) { + mod = g_array_index(a, module*, i); + if (!mod) + continue; + module_release(srv->modules, mod); + } + + g_array_free(srv->modules->mods, TRUE); + g_slice_free(modules, srv->modules); + + if (srv->module_dir) + g_free(srv->module_dir); +} + + +module* module_load(modules *mods, const gchar* name) { + module *mod; + ModuleInit m_init; + GString *m_init_str, *m_free_str; + guint i; + + mod = module_lookup(mods, name); + + if (mod) { + /* module already loaded, increment refcount and return */ + mod->refcount++; + return mod; + } + + mod = g_slice_new0(module); + mod->name = g_string_new(name); + mod->refcount = 1; + mod->path = g_module_build_path(((server *)mods->main)->module_dir, name); + TRACE(mods->main, "loading module '%s' from path: %s", name, mod->path); + + mod->module = g_module_open(mod->path, G_MODULE_BIND_LAZY); + + if (!mod->module) { + g_string_free(mod->name, TRUE); + g_free(mod->path); + g_slice_free(module, mod); + return NULL; + } + + /* temporary strings for mod_xyz_init and mod_xyz_free */ + m_init_str = g_string_new(name); + g_string_append_len(m_init_str, CONST_STR_LEN("_init")); + m_free_str = g_string_new(name); + g_string_append_len(m_free_str, CONST_STR_LEN("_free")); + + if (!g_module_symbol(mod->module, m_init_str->str, (gpointer *)&m_init) + || !g_module_symbol(mod->module, m_free_str->str, (gpointer *)&mod->free) + || m_init == NULL || mod->free == NULL) { + + /* mod_init or mod_free couldn't be located, something went wrong */ + g_string_free(m_init_str, TRUE); + g_string_free(m_free_str, TRUE); + g_free(mod->path); + g_string_free(mod->name, TRUE); + g_slice_free(module, mod); + return NULL; + } + + /* call mod_xyz_init */ + if (!m_init(mods, mod)) { + g_string_free(m_init_str, TRUE); + g_string_free(m_free_str, TRUE); + g_free(mod->path); + g_string_free(mod->name, TRUE); + g_slice_free(module, mod); + return NULL; + } + + /* insert into free slot */ + for (i = 0; i < mods->mods->len; i++) { + if (!g_array_index(mods->mods, module*, i)) + { + g_array_index(mods->mods, module*, i) = mod; + break; + } + } + + /* if no free slot was found, append */ + if (i == mods->mods->len) + g_array_append_val(mods->mods, mod); + + /* free temp strings */ + g_string_free(m_free_str, TRUE); + g_string_free(m_init_str, TRUE); + + return mod; +} + +void module_release(modules *mods, module *mod) { + guint i; + + if (--mod->refcount > 0) + return; + + for (i = 0; i < mods->mods->len; i++) { + if (g_array_index(mods->mods, module*, i) == mod) + { + g_array_index(mods->mods, module*, i) = NULL; + break; + } + } + + mod->free(mods, mod); + g_module_close(mod->module); + g_free(mod->path); + g_string_free(mod->name, TRUE); + g_slice_free(module, mod); +} + +void module_release_name(modules *mods, const gchar* name) { + module *mod = module_lookup(mods, name); + + if (mod) + module_release(mods, mod); +} diff --git a/src/module.h b/src/module.h index 0a8e3c1..e68a32a 100644 --- a/src/module.h +++ b/src/module.h @@ -6,14 +6,14 @@ #define MODULE_VERSION ((guint) 0x00000001) #define MODULE_VERSION_CHECK(mods) do { \ if (mods->version != MODULE_VERSION) { \ - ERROR("Version mismatch for modules system: is %u, expected %u", mods->version, MODULE_VERSION); \ + ERROR(mods->main, "Version mismatch for modules system: is %u, expected %u", mods->version, MODULE_VERSION); \ return FALSE; \ } } while(0) /** see module_load */ #define MODULE_DEPENDS(mods, name) do { \ - if (!modules_load(mods, name)) { \ - ERROR("Couldn't load dependency '%s'", name); \ + if (!module_load(mods, name)) { \ + ERROR(mods->main, "Couldn't load dependency '%s'", name); \ return FALSE; \ } } while(0) @@ -29,21 +29,23 @@ typedef gboolean (*ModuleFree)(modules *mods, module *mod); struct module { gint refcount; /**< count how often module is used. module gets unloaded if refcount reaches zero. */ - gchar *name; /**< name of module, can be set my plugin_init */ + GString *name; /**< name of module, can be set my plugin_init */ GModule *module; /**< glib handle */ - + gchar *path; /**< path to the module file */ + gpointer config; /**< private module data */ ModuleFree free; /**< if set by plugin_init it gets called before module is unloaded */ }; struct modules { guint version; /**< api version */ - - GHashTable *mods; /**< hash table of modules */ + + GArray *mods; /**< array of (module*) */ gpointer main; /**< pointer to a application specific main structure, e.g. server */ }; LI_API modules* modules_init(gpointer main); +LI_API void modules_cleanup(server *srv); /** Loads a module if not loaded yet and returns the module struct for it (after increasing refcount) * returns NULL if it couldn't load the module. @@ -52,7 +54,7 @@ LI_API modules* modules_init(gpointer main); LI_API module* module_load(modules *mods, const gchar* name); LI_API void module_acquire(module *mod); -LI_API void module_release(modules *mods, module *module); -LI_API void module_release_name(modules *mods, const char* name); +LI_API void module_release(modules *mods, module *mod); +LI_API void module_release_name(modules *mods, const gchar* name); #endif diff --git a/src/plugin.c b/src/plugin.c index 20d1e14..d6612f4 100644 --- a/src/plugin.c +++ b/src/plugin.c @@ -3,6 +3,7 @@ #include "log.h" static gboolean plugin_load_default_option(server *srv, server_option *sopt); +static void plugin_free_default_options(server *srv, plugin *p); static plugin* plugin_new(const gchar *name) { plugin *p = g_slice_new0(plugin); @@ -58,6 +59,7 @@ void plugin_free(server *srv, plugin *p) { } g_hash_table_remove(srv->plugins, p->name); + plugin_free_default_options(srv, p); plugin_free_options(srv, p); plugin_free_actions(srv, p); plugin_free_setups(srv, p); @@ -89,22 +91,22 @@ void server_plugins_free(server *srv) { g_hash_table_destroy(srv->setups); } -gboolean plugin_register(server *srv, const gchar *name, PluginInit init) { +plugin *plugin_register(server *srv, const gchar *name, PluginInit init) { plugin *p; if (!init) { ERROR(srv, "Module '%s' needs an init function", name); - return FALSE; + return NULL; } if (g_atomic_int_get(&srv->state) != SERVER_STARTING) { ERROR(srv, "Cannot register plugin '%s' after server was started", name); - return FALSE; + return NULL; } if (g_hash_table_lookup(srv->plugins, name)) { ERROR(srv, "Module '%s' already registered", name); - return FALSE; + return NULL; } p = plugin_new(name); @@ -125,7 +127,7 @@ gboolean plugin_register(server *srv, const gchar *name, PluginInit init) { so->p ? so->p->name : "", p->name); plugin_free(srv, p); - return FALSE; + return NULL; } so = g_slice_new0(server_option); so->type = po->type; @@ -152,7 +154,7 @@ gboolean plugin_register(server *srv, const gchar *name, PluginInit init) { sa->p ? sa->p->name : "", p->name); plugin_free(srv, p); - return FALSE; + return NULL; } sa = g_slice_new0(server_action); sa->create_action = pa->create_action; @@ -173,7 +175,7 @@ gboolean plugin_register(server *srv, const gchar *name, PluginInit init) { ss->p ? ss->p->name : "", p->name); plugin_free(srv, p); - return FALSE; + return NULL; } ss = g_slice_new0(server_setup); ss->setup = ps->setup; @@ -182,7 +184,7 @@ gboolean plugin_register(server *srv, const gchar *name, PluginInit init) { } } - return TRUE; + return p; } @@ -396,7 +398,7 @@ static gboolean plugin_load_default_option(server *srv, server_option *sopt) { return TRUE; } -void plugins_free_default_options(server *srv) { +static void plugin_free_default_options(server *srv, plugin *p) { static const option_value oempty = {0}; GHashTableIter iter; gpointer k, v; @@ -408,6 +410,9 @@ void plugins_free_default_options(server *srv) { mark.sopt = sopt; mark.ndx = sopt->index; + if (sopt->p != p) + continue; + mark.value = g_array_index(srv->option_def_values, option_value, sopt->index); release_option(srv, &mark); diff --git a/src/plugin.h b/src/plugin.h index b5af66f..e6c3ed7 100644 --- a/src/plugin.h +++ b/src/plugin.h @@ -133,7 +133,7 @@ struct server_setup { }; /* Needed by modules to register their plugin(s) */ -LI_API gboolean plugin_register(server *srv, const gchar *name, PluginInit init); +LI_API plugin *plugin_register(server *srv, const gchar *name, PluginInit init); /* Internal needed functions */ LI_API void plugin_free(server *srv, plugin *p); @@ -156,9 +156,6 @@ LI_API action* create_action(server *srv, const gchar *name, value *value); /** For setup function, e.g. 'listen "127.0.0.1:8080"'; free value after call */ LI_API gboolean call_setup(server *srv, const char *name, value *val); -LI_API void plugins_free_default_options(server *srv); - - /** free val after call */ LI_API gboolean plugin_set_default_option(server *srv, const gchar* name, value *val); diff --git a/src/plugin_core.c b/src/plugin_core.c index e68d461..336bf62 100644 --- a/src/plugin_core.c +++ b/src/plugin_core.c @@ -150,6 +150,7 @@ static action_result core_handle_static(connection *con, gpointer param) { } else { struct stat st; fstat(fd, &st); + #ifdef FD_CLOEXEC fcntl(fd, F_SETFD, FD_CLOEXEC); #endif @@ -450,6 +451,57 @@ static gboolean core_workers(server *srv, plugin* p, value *val) { return TRUE; } +static gboolean core_module_load(server *srv, plugin* p, value *val) { + value *mods = value_new_list(); + UNUSED(p); + + if (!g_module_supported()) { + ERROR(srv, "%s", "module loading not supported on this platform"); + value_free(mods); + return FALSE; + } + + if (val->type == VALUE_STRING) { + /* load only one module */ + value *name = value_new_string(value_extract(val).string); + g_array_append_val(mods->data.list, name); + } else if (val->type == VALUE_LIST) { + /* load a list of modules */ + for (guint i = 0; i < val->data.list->len; i++) { + value *v = g_array_index(val->data.list, value*, i); + if (v->type != VALUE_STRING) { + ERROR(srv, "module_load takes either a string or a list of strings as parameter, list with %s entry given", value_type_string(v->type)); + value_free(mods); + return FALSE; + } + } + mods->data.list = value_extract(val).list; + } else { + ERROR(srv, "module_load takes either a string or a list of strings as parameter, %s given", value_type_string(val->type)); + return FALSE; + } + + /* parameter types ok, load modules */ + for (guint i = 0; i < mods->data.list->len; i++) { + GString *name = g_array_index(mods->data.list, value*, i)->data.string; + if (!module_load(srv->modules, name->str)) { + ERROR(srv, "could not load module '%s': %s", name->str, g_module_error()); + value_free(mods); + return FALSE; + } + + TRACE(srv, "loaded module '%s'", name->str); + } + + value_free(mods); + + return TRUE; +} + +/* + * OPTIONS + */ + static gboolean core_option_log_parse(server *srv, plugin *p, size_t ndx, value *val, option_value *oval) { GHashTableIter iter; gpointer k, v; @@ -741,6 +793,7 @@ static const plugin_setup setups[] = { { "listen", core_listen }, { "event_handler", core_event_handler }, { "workers", core_workers }, + { "module_load", core_module_load }, { NULL, NULL } }; diff --git a/src/server.c b/src/server.c index fbb7aa3..3516869 100644 --- a/src/server.c +++ b/src/server.c @@ -60,6 +60,8 @@ server* server_new() { srv->sockets = g_array_new(FALSE, TRUE, sizeof(server_socket*)); + srv->modules = modules_init(srv); + srv->plugins = g_hash_table_new(g_str_hash, g_str_equal); srv->options = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, server_value_free); srv->actions = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, server_action_free); @@ -94,6 +96,14 @@ void server_free(server* srv) { } } + action_release(srv, srv->mainaction); + + /* release modules */ + modules_cleanup(srv); + + plugin_free(srv, srv->core_plugin); + g_array_free(srv->option_def_values, TRUE); + /* free all workers */ { guint i; @@ -121,13 +131,6 @@ void server_free(server* srv) { g_array_free(srv->sockets, TRUE); } - action_release(srv, srv->mainaction); - - if (srv->option_def_values->len) { - plugins_free_default_options(srv); - } - g_array_free(srv->option_def_values, TRUE); - log_cleanup(srv); server_plugins_free(srv); diff --git a/src/server.h b/src/server.h index 97d90d6..abf937e 100644 --- a/src/server.h +++ b/src/server.h @@ -41,7 +41,11 @@ struct server { GArray *sockets; /** array of (server_socket*) */ + gchar *module_dir; + struct modules *modules; + GHashTable *plugins; /**< const gchar* => (plugin*) */ + struct plugin *core_plugin; /* registered by plugins */ GHashTable *options; /**< const gchar* => (server_option*) */ diff --git a/src/wscript b/src/wscript index 90542e8..e830539 100644 --- a/src/wscript +++ b/src/wscript @@ -4,7 +4,7 @@ #import Object, Params, os, sys import Options -common_uselib = 'glib gthread' +common_uselib = 'glib gthread gmodule' common_source=''' actions.c @@ -20,6 +20,7 @@ common_source=''' http_headers.c http_request_parser.rl log.c + module.c network.c network_write.c network_writev.c network_linux_sendfile.c @@ -74,7 +75,7 @@ main_source = ''' def lighty_mod(bld, target, src, uselib = '', option = ''): if option and not getattr(Options.options, option): return - mod = bld.new_task_gen('cc', 'plugin') + mod = bld.new_task_gen('cc', 'shlib') mod.target = target mod.source = src mod.uselib += 'lightymod ' + common_uselib + uselib diff --git a/wscript b/wscript index a83efb7..fe2ed8a 100644 --- a/wscript +++ b/wscript @@ -36,7 +36,7 @@ def set_options(opt): opt.add_option('--build-static', action='store_true', help='build a static lighttpd with all modules added', dest = 'buildstatic', default = False) opt.add_option('--append', action='store', help='Append string to binary names / library dir', dest = 'append', default = '') opt.add_option('--lib-dir', action='store', help='Module directory [default: prefix + /lib/lighttpd + append]', dest = 'libdir', default = '') - + opt.add_option('--debug', action='store_true', help='Do not compile with -O2', dest = 'debug', default = False) from Tools.config_c import enumerator_base, check_data @@ -396,6 +396,10 @@ def configure(conf): incdir = conf.env['CPPPATH_gthread'][0] conf.env['CPPPATH_gthread'] += [ incdir+'/glib-2.0/', incdir + '/glib-2.0/include/' ] + PKGCONFIG(conf, "gmodule-2.0", uselib = 'gmodule', mandatory = 1) + incdir = conf.env['CPPPATH_gmodule'][0] + conf.env['CPPPATH_gmodule'] += [ incdir+'/glib-2.0/', incdir + '/glib-2.0/include/' ] + #if opts.libfcgi: #CHECK_INCLUDE_FILES(conf, "fastcgi.h", "HAVE_FASTCGI_H", uselib = 'libfcgi') #if not conf.is_defined("HAVE_FASTCGI_H"):