From 176b1c282a54b2547b5795238120e842fcaeb6bd Mon Sep 17 00:00:00 2001 From: =?utf8?q?Vladim=C3=ADr=20=C4=8Cun=C3=A1t?= Date: Thu, 14 Mar 2019 13:09:48 +0100 Subject: [PATCH] module API+ABI: remove one level of indirection ... for layers and props. This breaks C module API+ABI. It seemed weird to repeatedly call a function that returns a pointer to a structure in which we find the function we want to actually call. We've never used changing these functions AFAIK, and the target functions could easily be written to change their behavior instead (i.e. move the indirection *inside* the function). When breaking this, I also removed these two (_layers and _props) from the dynamic symbols (to be) exported from the C modules. They always pointed to memory belonging inside the module, and they seem quite sensible to be set up by the _init symbol instead. --- daemon/engine.c | 7 ++-- daemon/ffimodule.c | 13 ++---- lib/layer/cache.c | 11 ++--- lib/layer/iterate.c | 9 +++-- lib/layer/validate.c | 14 +++---- lib/module.c | 54 ++++++++++++------------- lib/module.h | 15 +++---- lib/resolve.c | 5 +-- lib/utils.c | 2 +- modules/README.rst | 2 + modules/bogus_log/bogus_log.c | 33 ++++++--------- modules/cookies/cookies.c | 41 ++++++++----------- modules/dnstap/dnstap.c | 17 ++++---- modules/edns_keepalive/edns_keepalive.c | 10 ++--- modules/hints/hints.c | 47 ++++++++------------- modules/nsid/nsid.c | 40 ++++++++---------- modules/stats/stats.c | 42 +++++++------------ 17 files changed, 147 insertions(+), 215 deletions(-) diff --git a/daemon/engine.c b/daemon/engine.c index 2eb57684e..9ba6d2b63 100644 --- a/daemon/engine.c +++ b/daemon/engine.c @@ -662,13 +662,13 @@ int engine_init(struct engine *engine, knot_mm_t *pool) return ret; } +/** Unregister a (found) module */ static void engine_unload(struct engine *engine, struct kr_module *module) { - /* Unregister module */ auto_free char *name = strdup(module->name); kr_module_unload(module); /* Clear in Lua world, but not for embedded modules ('cache' in particular). */ - if (name && !kr_module_embedded(name)) { + if (name && !kr_module_get_embedded(name)) { lua_pushnil(engine->L); lua_setglobal(engine->L, name); } @@ -821,8 +821,7 @@ static int register_properties(struct engine *engine, struct kr_module *module) REGISTER_MODULE_CALL(engine->L, module, module->config, "config"); } - const struct kr_prop *p = module->props == NULL ? NULL : module->props(); - for (; p && p->name; ++p) { + for (const struct kr_prop *p = module->props; p && p->name; ++p) { if (p->cb != NULL) { REGISTER_MODULE_CALL(engine->L, module, p->cb, p->name); } diff --git a/daemon/ffimodule.c b/daemon/ffimodule.c index 69fc58805..496be6fae 100644 --- a/daemon/ffimodule.c +++ b/daemon/ffimodule.c @@ -246,14 +246,6 @@ static kr_layer_api_t *l_ffi_layer_create(lua_State *L, struct kr_module *module return api; } -/** @internal Retrieve C layer api wrapper. */ -static const kr_layer_api_t *l_ffi_layer(struct kr_module *module) -{ - if (module) { - return (const kr_layer_api_t *)module->data; - } - return NULL; -} #undef LAYER_REGISTER int ffimodule_register_lua(struct engine *engine, struct kr_module *module, const char *name) @@ -278,8 +270,9 @@ int ffimodule_register_lua(struct engine *engine, struct kr_module *module, cons /* Bake layer API if defined in module */ lua_getfield(L, -1, "layer"); if (!lua_isnil(L, -1)) { - module->layer = &l_ffi_layer; - module->data = l_ffi_layer_create(L, module); + module->layer = l_ffi_layer_create(L, module); + /* most likely not needed, but compatibility for now */ + module->data = (void *)module->layer; } module->lib = L; lua_pop(L, 2); /* Clear the layer + module global */ diff --git a/lib/layer/cache.c b/lib/layer/cache.c index c7bbc1ab6..b1471bc50 100644 --- a/lib/layer/cache.c +++ b/lib/layer/cache.c @@ -18,14 +18,15 @@ #include "lib/cache/api.h" /** Module implementation. */ -const kr_layer_api_t *cache_layer(struct kr_module *module) +int cache_init(struct kr_module *self) { - static const kr_layer_api_t _layer = { + static const kr_layer_api_t layer = { .produce = &cache_peek, .consume = &cache_stash, }; - - return &_layer; + self->layer = &layer; + return kr_ok(); } -KR_MODULE_EXPORT(cache) +KR_MODULE_EXPORT(cache) /* useless for builtin module, but let's be consistent */ + diff --git a/lib/layer/iterate.c b/lib/layer/iterate.c index dbc3761a2..be0fb1844 100644 --- a/lib/layer/iterate.c +++ b/lib/layer/iterate.c @@ -1111,17 +1111,18 @@ static int resolve(kr_layer_t *ctx, knot_pkt_t *pkt) } /** Module implementation. */ -const kr_layer_api_t *iterate_layer(struct kr_module *module) +int iterate_init(struct kr_module *self) { - static const kr_layer_api_t _layer = { + static const kr_layer_api_t layer = { .begin = &begin, .reset = &reset, .consume = &resolve, .produce = &prepare_query }; - return &_layer; + self->layer = &layer; + return kr_ok(); } -KR_MODULE_EXPORT(iterate) +KR_MODULE_EXPORT(iterate) /* useless for builtin module, but let's be consistent */ #undef VERBOSE_MSG diff --git a/lib/layer/validate.c b/lib/layer/validate.c index 16e339602..59634bc30 100644 --- a/lib/layer/validate.c +++ b/lib/layer/validate.c @@ -1116,21 +1116,17 @@ static int validate(kr_layer_t *ctx, knot_pkt_t *pkt) VERBOSE_MSG(qry, "<= answer valid, OK\n"); return KR_STATE_DONE; } + /** Module implementation. */ -const kr_layer_api_t *validate_layer(struct kr_module *module) +int validate_init(struct kr_module *self) { - static const kr_layer_api_t _layer = { + static const kr_layer_api_t layer = { .consume = &validate, }; - /* Store module reference */ - return &_layer; -} - -int validate_init(struct kr_module *module) -{ + self->layer = &layer; return kr_ok(); } -KR_MODULE_EXPORT(validate) +KR_MODULE_EXPORT(validate) /* useless for builtin module, but let's be consistent */ #undef VERBOSE_MSG diff --git a/lib/module.c b/lib/module.c index e00370708..c88bd2452 100644 --- a/lib/module.c +++ b/lib/module.c @@ -23,16 +23,21 @@ #include "lib/utils.h" #include "lib/module.h" -/* List of embedded modules */ -const kr_layer_api_t *iterate_layer(struct kr_module *module); -const kr_layer_api_t *validate_layer(struct kr_module *module); -const kr_layer_api_t *cache_layer(struct kr_module *module); -static const struct kr_module embedded_modules[] = { - { "iterate", NULL, NULL, NULL, iterate_layer, NULL, NULL, NULL }, - { "validate", NULL, NULL, NULL, validate_layer, NULL, NULL, NULL }, - { "cache", NULL, NULL, NULL, cache_layer, NULL, NULL, NULL }, -}; +/* List of embedded modules. These aren't (un)loaded. */ +int iterate_init(struct kr_module *self); +int validate_init(struct kr_module *self); +int cache_init(struct kr_module *self); +kr_module_init_cb kr_module_get_embedded(const char *name) +{ + if (strcmp(name, "iterate") == 0) + return iterate_init; + if (strcmp(name, "validate") == 0) + return validate_init; + if (strcmp(name, "cache") == 0) + return cache_init; + return NULL; +} /** Load prefixed symbol. */ static void *load_symbol(void *lib, const char *prefix, const char *name) @@ -60,30 +65,16 @@ static int load_library(struct kr_module *module, const char *name, const char * return kr_error(ENOENT); } -const struct kr_module * kr_module_embedded(const char *name) -{ - for (unsigned i = 0; i < sizeof(embedded_modules)/sizeof(embedded_modules[0]); ++i) { - if (strcmp(name, embedded_modules[i].name) == 0) - return embedded_modules + i; - } - return NULL; -} - /** Load C module symbols. */ static int load_sym_c(struct kr_module *module, uint32_t api_required) { + module->init = kr_module_get_embedded(module->name); + if (module->init) { + return kr_ok(); + } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" /* casts after load_symbol() */ /* Check if it's embedded first */ - const struct kr_module *embedded = kr_module_embedded(module->name); - if (embedded) { - module->init = embedded->init; - module->deinit = embedded->deinit; - module->config = embedded->config; - module->layer = embedded->layer; - module->props = embedded->props; - return kr_ok(); - } /* Load dynamic library module */ auto_free char *m_prefix = kr_strcatdup(2, module->name, "_"); @@ -102,9 +93,14 @@ static int load_sym_c(struct kr_module *module, uint32_t api_required) ML(init); ML(deinit); ML(config); - ML(layer); - ML(props); #undef ML + if (load_symbol(module->lib, m_prefix, "layer") + || load_symbol(module->lib, m_prefix, "props")) { + /* In case someone re-compiled against new kresd + * but haven't actually changed the symbols. */ + kr_log_error("[system] module %s needs to change API.\n", module->name); + return kr_error(ENOTSUP); + } return kr_ok(); #pragma GCC diagnostic pop diff --git a/lib/module.h b/lib/module.h index 295c1f9ba..63f4cb801 100644 --- a/lib/module.h +++ b/lib/module.h @@ -35,7 +35,7 @@ struct kr_prop; */ #define KR_MODULE_EXPORT(module) \ KR_EXPORT uint32_t module ## _api() { return KR_MODULE_API; } -#define KR_MODULE_API ((uint32_t) 0x20180401) +#define KR_MODULE_API ((uint32_t) 0x20190314) typedef uint32_t (module_api_cb)(void); @@ -55,10 +55,10 @@ struct kr_module { int (*deinit)(struct kr_module *self); /** Configure with encoded JSON (NULL if missing). @return error code. */ int (*config)(struct kr_module *self, const char *input); - /** Get a pointer to packet processing API specs. See docs on that type. */ - const kr_layer_api_t * (*layer)(struct kr_module *self); - /** Get a pointer to list of properties, terminated by { NULL, NULL, NULL }. */ - const struct kr_prop * (*props)(void); + /** Packet processing API specs. May be NULL. See docs on that type. */ + const kr_layer_api_t *layer; + /** List of properties. May be NULL. Terminated by { NULL, NULL, NULL }. */ + const struct kr_prop *props; void *lib; /**< Shared library handle or RTLD_DEFAULT */ void *data; /**< Custom data context. */ @@ -103,9 +103,10 @@ int kr_module_load(struct kr_module *module, const char *name, const char *path) KR_EXPORT void kr_module_unload(struct kr_module *module); +typedef int (*kr_module_init_cb)(struct kr_module *); /** - * Get embedded module prototype by name (or NULL). + * Get embedded module's init function by name (or NULL). */ KR_EXPORT -const struct kr_module * kr_module_embedded(const char *name); +kr_module_init_cb kr_module_get_embedded(const char *name); diff --git a/lib/resolve.c b/lib/resolve.c index 2493bb1a3..2d97e900e 100644 --- a/lib/resolve.c +++ b/lib/resolve.c @@ -108,7 +108,7 @@ static int answer_finalize_yield(kr_layer_t *ctx) { return kr_ok(); } for (size_t i = (from); i < (r)->ctx->modules->len; ++i) { \ struct kr_module *mod = (r)->ctx->modules->at[i]; \ if (mod->layer) { \ - struct kr_layer layer = {.state = (r)->state, .api = mod->layer(mod), .req = (r)}; \ + struct kr_layer layer = {.state = (r)->state, .api = mod->layer, .req = (r)}; \ if (layer.api && layer.api->func) { \ (r)->state = layer.api->func(&layer, ##__VA_ARGS__); \ if ((r)->state == KR_STATE_YIELD) { \ @@ -127,8 +127,7 @@ static int answer_finalize_yield(kr_layer_t *ctx) { return kr_ok(); } static inline size_t layer_id(struct kr_request *req, const struct kr_layer_api *api) { module_array_t *modules = req->ctx->modules; for (size_t i = 0; i < modules->len; ++i) { - struct kr_module *mod = modules->at[i]; - if (mod->layer && mod->layer(mod) == api) { + if (modules->at[i]->layer == api) { return i; } } diff --git a/lib/utils.c b/lib/utils.c index 8f4aee6dd..ee13a4a85 100644 --- a/lib/utils.c +++ b/lib/utils.c @@ -766,7 +766,7 @@ static char *callprop(struct kr_module *module, const char *prop, const char *in if (!module || !module->props || !prop) { return NULL; } - for (const struct kr_prop *p = module->props(); p && p->name; ++p) { + for (const struct kr_prop *p = module->props; p && p->name; ++p) { if (p->cb != NULL && strcmp(p->name, prop) == 0) { return p->cb(env, module, input); } diff --git a/modules/README.rst b/modules/README.rst index b15f3ca35..d0ee84d55 100644 --- a/modules/README.rst +++ b/modules/README.rst @@ -16,6 +16,8 @@ Currently modules written in C and LuaJIT are supported. The anatomy of an extension =========================== +FIXME: review and fix. + A module is a shared object or script defining specific functions, here's an overview. *Note* |---| the :ref:`Modules ` header documents the module loading and API. diff --git a/modules/bogus_log/bogus_log.c b/modules/bogus_log/bogus_log.c index 98c0fc919..09f4d6828 100644 --- a/modules/bogus_log/bogus_log.c +++ b/modules/bogus_log/bogus_log.c @@ -105,6 +105,18 @@ static char* dump_frequent(void *env, struct kr_module *module, const char *args KR_EXPORT int bogus_log_init(struct kr_module *module) { + static kr_layer_api_t layer = { + .consume = &consume, + }; + layer.data = module; + module->layer = &layer; + + static const struct kr_prop props[] = { + { &dump_frequent, "frequent", "List most frequent queries.", }, + { NULL, NULL, NULL } + }; + module->props = props; + struct stat_data *data = malloc(sizeof(*data)); if (!data) { return kr_error(ENOMEM); @@ -126,25 +138,4 @@ int bogus_log_deinit(struct kr_module *module) return kr_ok(); } - -KR_EXPORT -const kr_layer_api_t *bogus_log_layer(struct kr_module *module) -{ - static kr_layer_api_t _layer = { - .consume = &consume, - }; - _layer.data = module; - return &_layer; -} - -KR_EXPORT -struct kr_prop *bogus_log_props(void) -{ - static struct kr_prop prop_list[] = { - { &dump_frequent, "frequent", "List most frequent queries.", }, - { NULL, NULL, NULL } - }; - return prop_list; -} - KR_MODULE_EXPORT(bogus_log) diff --git a/modules/cookies/cookies.c b/modules/cookies/cookies.c index 6a1db4f24..c7a350c3a 100644 --- a/modules/cookies/cookies.c +++ b/modules/cookies/cookies.c @@ -47,6 +47,22 @@ static char *cookies_config(void *env, struct kr_module *module, KR_EXPORT int cookies_init(struct kr_module *module) { + /* The function answer_finalize() in resolver is called before any + * .finish callback. Therefore this layer does not use it. */ + static kr_layer_api_t layer = { + .begin = &check_request, + .consume = &check_response + }; + /* Store module reference */ + layer.data = module; + module->layer = &layer; + + static const struct kr_prop props[] = { + { &cookies_config, "config", "Empty value to return current configuration.", }, + { NULL, NULL, NULL } + }; + module->props = props; + struct engine *engine = module->data; struct kr_cookie_ctx *cookie_ctx = &engine->resolver.cookie_ctx; @@ -72,29 +88,4 @@ int cookies_deinit(struct kr_module *module) return kr_ok(); } -KR_EXPORT -const kr_layer_api_t *cookies_layer(struct kr_module *module) -{ - /* The function answer_finalize() in resolver is called before any - * .finish callback. Therefore this layer does not use it. */ - - static kr_layer_api_t _layer = { - .begin = &check_request, - .consume = &check_response - }; - /* Store module reference */ - _layer.data = module; - return &_layer; -} - -KR_EXPORT -struct kr_prop *cookies_props(void) -{ - static struct kr_prop prop_list[] = { - { &cookies_config, "config", "Empty value to return current configuration.", }, - { NULL, NULL, NULL } - }; - return prop_list; -} - KR_MODULE_EXPORT(cookies) diff --git a/modules/dnstap/dnstap.c b/modules/dnstap/dnstap.c index 091842604..57c22c185 100644 --- a/modules/dnstap/dnstap.c +++ b/modules/dnstap/dnstap.c @@ -211,6 +211,13 @@ static int dnstap_log(kr_layer_t *ctx) { KR_EXPORT int dnstap_init(struct kr_module *module) { + static kr_layer_api_t layer = { + .finish = &dnstap_log, + }; + /* Store module reference */ + layer.data = module; + module->layer = &layer; + /* allocated memory for internal data */ struct dnstap_data *data = malloc(sizeof(*data)); if (!data) { @@ -368,15 +375,5 @@ int dnstap_config(struct kr_module *module, const char *conf) { return kr_ok(); } -KR_EXPORT -const kr_layer_api_t *dnstap_layer(struct kr_module *module) { - static kr_layer_api_t _layer = { - .finish = &dnstap_log, - }; - /* Store module reference */ - _layer.data = module; - return &_layer; -} - KR_MODULE_EXPORT(dnstap) diff --git a/modules/edns_keepalive/edns_keepalive.c b/modules/edns_keepalive/edns_keepalive.c index 99c0d9661..1a598638a 100644 --- a/modules/edns_keepalive/edns_keepalive.c +++ b/modules/edns_keepalive/edns_keepalive.c @@ -62,15 +62,13 @@ static int edns_keepalive_finalize(kr_layer_t *ctx) return ctx->state; } -KR_EXPORT -const kr_layer_api_t *edns_keepalive_layer(struct kr_module *module) +KR_EXPORT int edns_keeapalive_init(struct kr_module *self) { - static kr_layer_api_t _layer = { + static const kr_layer_api_t layer = { .answer_finalize = &edns_keepalive_finalize, }; - /* Store module reference */ - _layer.data = module; - return &_layer; + self->layer = &layer; + return kr_ok(); } KR_MODULE_EXPORT(edns_keepalive) diff --git a/modules/hints/hints.c b/modules/hints/hints.c index 1f154ae7f..613dcc651 100644 --- a/modules/hints/hints.c +++ b/modules/hints/hints.c @@ -600,26 +600,30 @@ static char* hint_ttl(void *env, struct kr_module *module, const char *args) return result; } -/* - * Module implementation. - */ - +/** Basic initialization: get a memory pool, etc. */ KR_EXPORT -const kr_layer_api_t *hints_layer(struct kr_module *module) +int hints_init(struct kr_module *module) { - static kr_layer_api_t _layer = { + static kr_layer_api_t layer = { .produce = &query, }; /* Store module reference */ - _layer.data = module; - return &_layer; -} + layer.data = module; + module->layer = &layer; + static const struct kr_prop props[] = { + { &hint_set, "set", "Set {name, address} hint.", }, + { &hint_del, "del", "Delete one {name, address} hint or all addresses for the name.", }, + { &hint_get, "get", "Retrieve hint for given name.", }, + { &hint_ttl, "ttl", "Set/get TTL used for the hints.", }, + { &hint_add_hosts, "add_hosts", "Load a file with hosts-like formatting and add contents into hints.", }, + { &hint_root, "root", "Replace root hints set (empty value to return current list).", }, + { &hint_root_file, "root_file", "Replace root hints set from a zonefile.", }, + { &hint_use_nodata, "use_nodata", "Synthesise NODATA if name matches, but type doesn't. True by default.", }, + { NULL, NULL, NULL } + }; + module->props = props; -/** Basic initialization: get a memory pool, etc. */ -KR_EXPORT -int hints_init(struct kr_module *module) -{ /* Create pool and copy itself */ knot_mm_t _pool = { .ctx = mp_new(4096), @@ -678,23 +682,6 @@ int hints_config(struct kr_module *module, const char *conf) return kr_ok(); } -KR_EXPORT -struct kr_prop *hints_props(void) -{ - static struct kr_prop prop_list[] = { - { &hint_set, "set", "Set {name, address} hint.", }, - { &hint_del, "del", "Delete one {name, address} hint or all addresses for the name.", }, - { &hint_get, "get", "Retrieve hint for given name.", }, - { &hint_ttl, "ttl", "Set/get TTL used for the hints.", }, - { &hint_add_hosts, "add_hosts", "Load a file with hosts-like formatting and add contents into hints.", }, - { &hint_root, "root", "Replace root hints set (empty value to return current list).", }, - { &hint_root_file, "root_file", "Replace root hints set from a zonefile.", }, - { &hint_use_nodata, "use_nodata", "Synthesise NODATA if name matches, but type doesn't. True by default.", }, - { NULL, NULL, NULL } - }; - return prop_list; -} - KR_MODULE_EXPORT(hints) #undef VERBOSE_MSG diff --git a/modules/nsid/nsid.c b/modules/nsid/nsid.c index fef6ecd3e..f5c8e6273 100644 --- a/modules/nsid/nsid.c +++ b/modules/nsid/nsid.c @@ -57,26 +57,6 @@ static int nsid_finalize(kr_layer_t *ctx) { return ctx->state; } -KR_EXPORT -const kr_layer_api_t *nsid_layer(struct kr_module *module) -{ - static kr_layer_api_t _layer = { - .answer_finalize = &nsid_finalize, - }; - _layer.data = module; - return &_layer; -} - -KR_EXPORT -int nsid_init(struct kr_module *module) { - struct nsid_config *config = calloc(1, sizeof(struct nsid_config)); - if (config == NULL) - return kr_error(ENOMEM); - - module->data = config; - return kr_ok(); -} - static char* nsid_name(void *env, struct kr_module *module, const char *args) { struct engine *engine = env; @@ -99,13 +79,25 @@ static char* nsid_name(void *env, struct kr_module *module, const char *args) } KR_EXPORT -struct kr_prop *nsid_props(void) -{ - static struct kr_prop prop_list[] = { +int nsid_init(struct kr_module *module) { + static kr_layer_api_t layer = { + .answer_finalize = &nsid_finalize, + }; + layer.data = module; + module->layer = &layer; + + static const struct kr_prop props[] = { { &nsid_name, "name", "Get or set local NSID value" }, { NULL, NULL, NULL } }; - return prop_list; + module->props = props; + + struct nsid_config *config = calloc(1, sizeof(struct nsid_config)); + if (config == NULL) + return kr_error(ENOMEM); + + module->data = config; + return kr_ok(); } KR_EXPORT diff --git a/modules/stats/stats.c b/modules/stats/stats.c index 5d377632c..add632eba 100644 --- a/modules/stats/stats.c +++ b/modules/stats/stats.c @@ -460,26 +460,29 @@ static char* dump_upstreams(void *env, struct kr_module *module, const char *arg return ret; } -/* - * Module implementation. - */ - KR_EXPORT -const kr_layer_api_t *stats_layer(struct kr_module *module) +int stats_init(struct kr_module *module) { - static kr_layer_api_t _layer = { + static kr_layer_api_t layer = { .consume = &collect_rtt, .finish = &collect, .begin = &collect_transport, }; /* Store module reference */ - _layer.data = module; - return &_layer; -} + layer.data = module; + module->layer = &layer; + + static const struct kr_prop props[] = { + { &stats_set, "set", "Set {key, val} metrics.", }, + { &stats_get, "get", "Get metrics for given key.", }, + { &stats_list, "list", "List observed metrics.", }, + { &dump_frequent, "frequent", "List most frequent queries.", }, + { &clear_frequent,"clear_frequent", "Clear frequent queries log.", }, + { &dump_upstreams, "upstreams", "List recently seen authoritatives.", }, + { NULL, NULL, NULL } + }; + module->props = props; -KR_EXPORT -int stats_init(struct kr_module *module) -{ struct stat_data *data = malloc(sizeof(*data)); if (!data) { return kr_error(ENOMEM); @@ -513,21 +516,6 @@ int stats_deinit(struct kr_module *module) return kr_ok(); } -KR_EXPORT -struct kr_prop *stats_props(void) -{ - static struct kr_prop prop_list[] = { - { &stats_set, "set", "Set {key, val} metrics.", }, - { &stats_get, "get", "Get metrics for given key.", }, - { &stats_list, "list", "List observed metrics.", }, - { &dump_frequent, "frequent", "List most frequent queries.", }, - { &clear_frequent,"clear_frequent", "Clear frequent queries log.", }, - { &dump_upstreams, "upstreams", "List recently seen authoritatives.", }, - { NULL, NULL, NULL } - }; - return prop_list; -} - KR_MODULE_EXPORT(stats) #undef VERBOSE_MSG -- 2.47.2