#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);
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);
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 */
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);
/* 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();
}
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)
return;
}
+ free(module->name);
+
if (module->deinit) {
module->deinit(module);
}
/*
* 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) \
| `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.
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