From: Marek Vavruša Date: Wed, 18 Mar 2015 12:35:48 +0000 (+0100) Subject: lib/module: support for properties X-Git-Tag: v1.0.0-beta1~299^2~1 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=486c2a2cb113c89993a9bbc7c32f92ecf2d73e90;p=thirdparty%2Fknot-resolver.git lib/module: support for properties --- diff --git a/README.md b/README.md index 63eba77f1..c92483798 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ The Knot DNS Resolver is a minimalistic caching resolver implementation. The project provides both a resolver library and a small daemon. Modular architecture of the library keeps the core tiny and efficient, and provides -a state-machine like API for extensions. There are three built-in modules: *iterator*, *cache* and *stats*, -but each module can be flipped on and off. +a state-machine like API for extensions. There are two built-in modules: *iterator* and *cache*, +and each module can be flipped on and off. ### Try it out? @@ -52,5 +52,5 @@ right now. ``` $ ./daemon/kresolved -h -$ ./daemon/kresolved -a 127.0.0.1#53 +$ ./daemon/kresolved -a "127.0.0.1#53" ``` diff --git a/daemon/worker.c b/daemon/worker.c index 9a7ccc75b..e8a29aaa1 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -53,7 +53,6 @@ int worker_init(struct worker_ctx *worker, mm_ctx_t *mm) kr_context_register(&worker->resolve, "iterate"); kr_context_register(&worker->resolve, "itercache"); kr_context_register(&worker->resolve, "hints"); - kr_context_register(&worker->resolve, "gostats"); return KNOT_EOK; } diff --git a/lib/layer/iterate.c b/lib/layer/iterate.c index e881186ea..ee16f3caa 100644 --- a/lib/layer/iterate.c +++ b/lib/layer/iterate.c @@ -476,4 +476,4 @@ const knot_layer_api_t *iterate_layer(void) return &_layer; } -KR_MODULE_EXPORT(iterate); +KR_MODULE_EXPORT(iterate) diff --git a/lib/layer/itercache.c b/lib/layer/itercache.c index f9ac681e2..cb19343d0 100644 --- a/lib/layer/itercache.c +++ b/lib/layer/itercache.c @@ -278,4 +278,4 @@ const knot_layer_api_t *itercache_layer(void) return &_layer; } -KR_MODULE_EXPORT(itercache); +KR_MODULE_EXPORT(itercache) diff --git a/lib/module.c b/lib/module.c index e8603ae17..4d6d81807 100644 --- a/lib/module.c +++ b/lib/module.c @@ -7,18 +7,43 @@ #include "lib/utils.h" #include "lib/module.h" -/*! \brief Library extension. */ -static inline const char *library_ext(void) -{ +/** Library extension. */ #if defined(__APPLE__) - return ".dylib"; + #define LIBEXT ".dylib" #elif _WIN32 - return ".lib"; + #define LIBEXT ".lib" #else - return ".so"; -#endif -} - + #define LIBEXT ".so" +#endif + +/** Check ABI version, return error on mismatch. */ +#define ABI_CHECK(m, prefix, symname, required) do { \ + if ((m)->lib != RTLD_DEFAULT) { \ + module_api_cb *_api = NULL; \ + *(void **) (&_api) = load_symbol((m)->lib, (prefix), (symname)); \ + if (_api == NULL) { \ + return kr_error(ENOENT); \ + } \ + if (_api() != (required)) { \ + return kr_error(ENOTSUP); \ + } \ + }\ + } while (0) + +/** Load ABI by symbol names. */ +#define ABI_LOAD(m, prefix, s_init, s_deinit, s_config, s_layer, s_prop) do { \ + module_prop_cb *module_prop = NULL; \ + *(void **) (&(m)->init) = load_symbol((m)->lib, (prefix), (s_init)); \ + *(void **) (&(m)->deinit) = load_symbol((m)->lib, (prefix), (s_deinit)); \ + *(void **) (&(m)->config) = load_symbol((m)->lib, (prefix), (s_config)); \ + *(void **) (&(m)->layer) = load_symbol((m)->lib, (prefix), (s_layer)); \ + *(void **) (&module_prop) = load_symbol((m)->lib, (prefix), (s_prop)); \ + if (module_prop != NULL) { \ + (m)->props = module_prop(); \ + } \ +} while(0) + +/** Load prefixed symbol. */ static void *load_symbol(void *lib, const char *prefix, const char *name) { auto_free char *symbol = kr_strcatdup(2, prefix, name); @@ -27,12 +52,12 @@ static void *load_symbol(void *lib, const char *prefix, const char *name) static int load_library(struct kr_module *module, const char *name, const char *path) { - const char *ext = library_ext(); + /* Absolute or relative path (then only library search path is used). */ auto_free char *lib_path = NULL; if (path != NULL) { - lib_path = kr_strcatdup(4, path, "/", name, ext); + lib_path = kr_strcatdup(4, path, "/", name, LIBEXT); } else { - lib_path = kr_strcatdup(2, name, ext); + lib_path = kr_strcatdup(2, name, LIBEXT); } if (lib_path == NULL) { return kr_error(ENOMEM); @@ -48,6 +73,16 @@ static int load_library(struct kr_module *module, const char *name, const char * return kr_error(ENOENT); } +/** Load C module symbols. */ +static int load_sym_c(struct kr_module *module, uint32_t api_required) +{ + auto_free char *module_prefix = kr_strcatdup(2, module->name, "_"); + ABI_CHECK(module, module_prefix, "api", api_required); + ABI_LOAD(module, module_prefix, "init", "deinit", "config", "layer", "props"); + return kr_ok(); +} + +/** Bootstrap Go runtime from module. */ static int bootstrap_libgo(struct kr_module *module) { /* Check if linked against compatible libgo */ @@ -79,8 +114,8 @@ static int bootstrap_libgo(struct kr_module *module) return kr_ok(); } - -static int load_libgo(struct kr_module *module, module_api_cb **module_api) +/** Load Go module symbols. */ +static int load_ffi_go(struct kr_module *module, uint32_t api_required) { /* Bootstrap libgo */ int ret = bootstrap_libgo(module); @@ -90,13 +125,8 @@ static int load_libgo(struct kr_module *module, module_api_cb **module_api) /* Enforced prefix for now. */ const char *module_prefix = "main."; - - *(void **) (module_api) = load_symbol(module->lib, module_prefix, "Api"); - *(void **) (&module->init) = load_symbol(module->lib, module_prefix, "Init"); - *(void **) (&module->deinit) = load_symbol(module->lib, module_prefix, "Deinit"); - *(void **) (&module->config) = load_symbol(module->lib, module_prefix, "Config"); - *(void **) (&module->layer) = load_symbol(module->lib, module_prefix, "Layer"); - + ABI_CHECK(module, module_prefix, "Api", api_required); + ABI_LOAD(module, module_prefix, "Init", "Deinit", "Config", "Layer", "Props"); return kr_ok(); } @@ -106,51 +136,40 @@ int kr_module_load(struct kr_module *module, const char *name, const char *path) return kr_error(EINVAL); } - /* Search for module library. */ + /* Initialize. */ memset(module, 0, sizeof(struct kr_module)); + module->name = strdup(name); + if (module->name == NULL) { + return kr_error(ENOMEM); + } + + /* Search for module library, use current namespace if not found. */ if (load_library(module, name, path) != 0) { /* Expand HOME env variable, as the linker may not expand it. */ auto_free char *local_path = kr_strcatdup(2, getenv("HOME"), "/.local" MODULEDIR); if (load_library(module, name, local_path) != 0) { - if (load_library(module, name, PREFIX MODULEDIR) != 0) { + if (load_library(module, name, PREFIX MODULEDIR) != 0) { + module->lib = RTLD_DEFAULT; } } } - /* It's okay if it fails, then current exec space is searched. */ - if (module->lib == NULL) { - module->lib = RTLD_DEFAULT; + /* Try to load module ABI. */ + int ret = load_sym_c(module, KR_MODULE_API); + if (ret != 0 && module->lib != RTLD_DEFAULT) { + ret = load_ffi_go(module, KR_MODULE_API); } - /* Load all symbols. */ - auto_free char *module_prefix = kr_strcatdup(2, name, "_"); - *(void **) (&module->init) = load_symbol(module->lib, module_prefix, "init"); - *(void **) (&module->deinit) = load_symbol(module->lib, module_prefix, "deinit"); - *(void **) (&module->config) = load_symbol(module->lib, module_prefix, "config"); - *(void **) (&module->layer) = load_symbol(module->lib, module_prefix, "layer"); - module_api_cb *module_api = NULL; - *(void **) (&module_api) = load_symbol(module->lib, module_prefix, "api"); - - /* No API version, try loading it as Go module. */ - if (module->lib != RTLD_DEFAULT && module_api == NULL) { - (void) load_libgo(module, &module_api); + /* Module constructor. */ + if (ret == 0 && module->init) { + ret = module->init(module); } - - /* Check module API version (if declared). */ - if (module_api == NULL) { - kr_module_unload(module); - return kr_error(KNOT_ENOENT); - } else if (module_api() != KR_MODULE_API) { + if (ret != 0) { kr_module_unload(module); - return kr_error(ENOTSUP); } - /* Initialize module */ - if (module->init) { - module->init(module); - } - return kr_ok(); + return ret; } void kr_module_unload(struct kr_module *module) @@ -159,6 +178,8 @@ void kr_module_unload(struct kr_module *module) return; } + free(module->name); + if (module->deinit) { module->deinit(module); } diff --git a/lib/module.h b/lib/module.h index 0e3442b3f..e946676a3 100644 --- a/lib/module.h +++ b/lib/module.h @@ -23,42 +23,66 @@ /* * Forward decls */ +struct kr_context; struct kr_module; +struct kr_prop; /* * API definition. */ -#define KR_MODULE_API 0x20150401 /*!< API version */ typedef uint32_t (module_api_cb)(void); typedef int (module_init_cb)(struct kr_module *); typedef int (module_deinit_cb)(struct kr_module *); typedef int (module_config_cb)(struct kr_module *, void *); typedef const knot_layer_api_t* (module_layer_cb)(void); +typedef struct kr_prop *(module_prop_cb)(void); -/*! Loaded module representation. */ +#define KR_MODULE_API ((uint32_t) 0x20150401) + +/** + * Module property (named callable). + * A module property has a free-form JSON output (and optional input). + */ +struct kr_prop { + char *(*cb)(struct kr_context*,struct kr_module *,const char*); + const char *name; + const char *info; +}; + +/** + * Module representation. + */ struct kr_module { - module_init_cb *init; /*!< Constructor */ - module_deinit_cb *deinit; /*!< Destructor */ - module_config_cb *config; /*!< Configuration */ - module_layer_cb *layer; /*!< Layer getter */ - void *lib; /*!< Shared library handle or RTLD_DEFAULT */ - void *data; /*!< Custom data context. */ + char *name; /**< Name. */ + module_init_cb *init; /**< Constructor */ + module_deinit_cb *deinit; /**< Destructor */ + module_config_cb *config; /**< Configuration */ + module_layer_cb *layer; /**< Layer getter */ + struct kr_prop *props; /**< Properties */ + void *lib; /**< Shared library handle or RTLD_DEFAULT */ + void *data; /**< Custom data context. */ }; -/*! Load module instance into memory. +/** + * Load module instance into memory. + * * @param module module structure * @param name module name * @param path module search path * @return 0 or an error */ -int kr_module_load(struct kr_module *module, const char *name, const char *path); +int kr_module_load(struct kr_module *module, const char *name, const char *path); -/*! Unload module instance. +/** + * Unload module instance. + * * @param module module structure */ void kr_module_unload(struct kr_module *module); -/*! Export module API version (place this at the end of your module). +/** + * Export module API version (place this at the end of your module). + * * @param module module name (f.e. hints) */ #define KR_MODULE_EXPORT(module) \ diff --git a/modules/README.md b/modules/README.md index 903353d52..c3d790131 100644 --- a/modules/README.md +++ b/modules/README.md @@ -92,6 +92,7 @@ A module is a shared library defining specific functions, here's an overview of | `module_deinit()` | `Deinit()` | `int` | `module` | ✕ | 0 | Destructor | | `module_config()` | `Config()` | `int` | `module, key` | ✕ | 0 | Configuration callback | | `module_layer()` | `Layer()` | `knot_layer_api_t*` | | ✕ | 0 | Returns module layer | +| `module_props()` | `Props()` | `struct kr_prop*` | | ✕ | 0 | Return NULL-terminated list of properties. | The `module_` corresponds to the module name, if the module name is `hints`, then the prefix for constructor would be `hints_init()`. This doesn't apply for Go, as it for now always implements `main` and requires capitalized first letter in order to export its symbol. @@ -230,6 +231,62 @@ func Layer() *C.knot_layer_api_t { See the [CGO][cgo] for more information about type conversions and interoperability between the C/Go. +### Configuring modules + +There is a callback `module_config()` but it's NOOP for now, as the configuration is not yet implemented. + +### Exposing module properties + +A module can offer NULL-terminated list of *properties*, each property is essentially a callable with free-form JSON input/output. +JSON was chosen as an interchangeable format that doesn't require any schema beforehand, so you can do two things - query the module properties +from external applications or between modules (i.e. `statistics` module can query `cache` module for memory usage). +JSON was chosen not because it's the most efficient protocol, but because it's easy to read and write and interface to outside world. +Here's an example how a module can expose its property: + +```c +static char* cached_size(struct kr_context *ctx, struct kr_module *module, const char *args) +{ + /* Parameters are ignored. */ + char *result = NULL; + namedb_txn_t txn; + int ret = kr_cache_txn_begin(ctx->cache, &txn, NAMEDB_RDONLY); + if (ret != 0) { + return NULL; + } + + /* For the sake of brevity... */ + asprintf(&result, "{ "cache_size": %d }\n", kr_cache_count(&txn)); + + kr_cache_txn_abort(&txn); + return result; +} + +struct kr_prop *cached_props(void) +{ + static struct kr_prop prop_list[] = { + /* Callback, Name, Description */ + { &cache_size, "size", "Return number of cached records.", }, + { NULL, NULL, NULL } + }; + return prop_list; +} + +KR_MODULE_EXPORT(cached) + +``` + +Once you load the module, you can call the module property from the interactive console: + +```sh +$ kresolved +... +> load cached +> cached.cached_size +{ "cache_size": 53 } +``` + +*Note* — this relies on function pointers, so the same `static inline` trick as for the `Layer()` is required for C/Go. + [lib]: lib/README.md [processing]: https://gitlab.labs.nic.cz/labs/knot/tree/master/src/libknot/processing [golang-syntax]: http://blog.golang.org/gos-declaration-syntax diff --git a/modules/hints/hints.c b/modules/hints/hints.c index fbcfd87ba..61c420ee9 100644 --- a/modules/hints/hints.c +++ b/modules/hints/hints.c @@ -189,4 +189,4 @@ int hints_deinit(struct kr_module *module) return kr_ok(); } -KR_MODULE_EXPORT(hints); +KR_MODULE_EXPORT(hints) diff --git a/tests/mock_cmodule.c b/tests/mock_cmodule.c index b81b8d2fe..45a2e2860 100644 --- a/tests/mock_cmodule.c +++ b/tests/mock_cmodule.c @@ -30,4 +30,4 @@ int mock_cmodule_deinit(struct kr_module *module) return kr_ok(); } -KR_MODULE_EXPORT(mock_cmodule); +KR_MODULE_EXPORT(mock_cmodule)