Commit 486c2a2c authored by Marek Vavruša's avatar Marek Vavruša

lib/module: support for properties

parent 95446fe0
......@@ -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"
```
......@@ -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;
}
......
......@@ -476,4 +476,4 @@ const knot_layer_api_t *iterate_layer(void)
return &_layer;
}
KR_MODULE_EXPORT(iterate);
KR_MODULE_EXPORT(iterate)
......@@ -278,4 +278,4 @@ const knot_layer_api_t *itercache_layer(void)
return &_layer;
}
KR_MODULE_EXPORT(itercache);
KR_MODULE_EXPORT(itercache)
......@@ -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);
}
......
......@@ -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) \
......
......@@ -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* &mdash; 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
......
......@@ -189,4 +189,4 @@ int hints_deinit(struct kr_module *module)
return kr_ok();
}
KR_MODULE_EXPORT(hints);
KR_MODULE_EXPORT(hints)
......@@ -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)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment