]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
lib/module: support for properties
authorMarek Vavruša <marek.vavrusa@nic.cz>
Wed, 18 Mar 2015 12:35:48 +0000 (13:35 +0100)
committerMarek Vavruša <marek.vavrusa@nic.cz>
Wed, 18 Mar 2015 12:35:48 +0000 (13:35 +0100)
README.md
daemon/worker.c
lib/layer/iterate.c
lib/layer/itercache.c
lib/module.c
lib/module.h
modules/README.md
modules/hints/hints.c
tests/mock_cmodule.c

index 63eba77f11301b32a2c18fef94228349a2c726c4..c9248379803492e3cc35dd4d4ddb5571a19908b5 100644 (file)
--- 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"
 ```
index 9a7ccc75bea6ef4542f616e9cf93edecb6bbe947..e8a29aaa1ce476c59d475c56c66a45b1b86c2c6d 100644 (file)
@@ -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;
 }
index e881186ea44b120560986ce32258b097cc0eab9f..ee16f3caa751c059d6d14ba1ea80a827dcad862f 100644 (file)
@@ -476,4 +476,4 @@ const knot_layer_api_t *iterate_layer(void)
        return &_layer;
 }
 
-KR_MODULE_EXPORT(iterate);
+KR_MODULE_EXPORT(iterate)
index f9ac681e214a5fc86bbb6c9d14cb03ef5156440e..cb19343d05e87a8732ce2b9bbb1b864a59e0e7c9 100644 (file)
@@ -278,4 +278,4 @@ const knot_layer_api_t *itercache_layer(void)
        return &_layer;
 }
 
-KR_MODULE_EXPORT(itercache);
+KR_MODULE_EXPORT(itercache)
index e8603ae175334119857b1509294924c6408478be..4d6d8180719fad2299be336b364b1c93edabc61a 100644 (file)
@@ -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);
        }
index 0e3442b3f3517c40bcafa11ae58a6d8849099a22..e946676a34c303bd4a25799e49e1e50770533a92 100644 (file)
 /*
  * 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) \
index 903353d52190fec049ddd3f28ce916bbe4bfc04a..c3d7901316adb03c1483f36996c53093529af908 100644 (file)
@@ -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
index fbcfd87ba795a2aa242b9a7413e77904c890523b..61c420ee95310b75d376b8a9caa38a1e61654382 100644 (file)
@@ -189,4 +189,4 @@ int hints_deinit(struct kr_module *module)
        return kr_ok();
 }
 
-KR_MODULE_EXPORT(hints);
+KR_MODULE_EXPORT(hints)
index b81b8d2fe14d9b368dc43fd0cd95f6030095bb3e..45a2e28601607ff820664b6c1f0601fbec7b85da 100644 (file)
@@ -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)