]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
daemon/ffimodule: support for Lua modules (including layers)
authorMarek Vavruša <marek.vavrusa@nic.cz>
Thu, 7 May 2015 08:35:53 +0000 (10:35 +0200)
committerMarek Vavruša <marek.vavrusa@nic.cz>
Thu, 7 May 2015 08:35:53 +0000 (10:35 +0200)
daemon/daemon.mk
daemon/engine.c
daemon/ffimodule.c [new file with mode: 0644]
daemon/ffimodule.h [new file with mode: 0644]

index a7e52a30d6539453b3e1505bc3a0061e36328058..6cfc44db759404730adeea3525f0c5972835873f 100644 (file)
@@ -4,6 +4,7 @@ kresolved_SOURCES := \
        daemon/engine.c      \
        daemon/worker.c      \
        daemon/bindings.c    \
+       daemon/ffimodule.c   \
        daemon/main.c
 
 # Embed resources
index 8507b0849fa8896e86fa4015c48f0b8ad44fcaba..d1fc3a325e3642e6eb1768f63fe6ce4dfff2021a 100644 (file)
@@ -22,6 +22,7 @@
 
 #include "daemon/engine.h"
 #include "daemon/bindings.h"
+#include "daemon/ffimodule.h"
 #include "lib/cache.h"
 #include "lib/defines.h"
 
@@ -95,59 +96,6 @@ static int l_trampoline(lua_State *L)
        return 0;
 }
 
-/** @internal Helper for retrieving the right function entrypoint. */
-static inline lua_State *l_ffi_preface(struct kr_module *module, const char *call) {
-       lua_State *L = module->lib;
-       lua_rawgeti(L, LUA_REGISTRYINDEX, (intptr_t)module->data);
-       lua_getfield(L, -1, call);
-       lua_pushlightuserdata(L, module);
-       return L;
-}
-
-/** @internal Helper for calling the entrypoint. */
-static inline int l_ffi_call(lua_State *L) {
-       if (engine_pcall(L, 1) != 0) {
-               lua_pop(L, 1);
-               return kr_error(EIO);
-       }
-       return lua_tonumber(L, 1);
-}
-
-static int l_ffi_init(struct kr_module *module)
-{
-       lua_State *L = l_ffi_preface(module, "init");
-       return l_ffi_call(L);
-}
-
-static int l_ffi_config(struct kr_module *module, const char *conf)
-{
-       lua_State *L = l_ffi_preface(module, "config");
-       lua_pushstring(L, conf);
-       return l_ffi_call(L);
-}
-
-static int l_ffi_deinit(struct kr_module *module)
-{
-       lua_State *L = l_ffi_preface(module, "deinit");
-       int ret = l_ffi_call(L);
-       /* Unref module and unset 'lib', so the module
-        * interface doesn't attempt to close it.
-        */
-       luaL_unref(L, LUA_REGISTRYINDEX, (intptr_t)module->data);
-       module->lib = NULL;
-       return ret;
-}
-
-static const knot_layer_api_t* l_ffi_layer(struct kr_module *module)
-{
-       /* lua_State *L = l_ffi_preface(module, "layer"); */
-       /** @todo Pickle access to context somewhere  */
-       /** @todo Store the returned table in the registry */
-       /** @todo Keep the reference in the api_t */
-       /** @todo Make trampoline functions for layer operations. */
-       return NULL;
-}
-
 /*
  * Engine API.
  */
@@ -297,6 +245,8 @@ static int engine_loadconf(struct engine *engine)
                lua_pop(engine->L, 1);
                return kr_error(ENOEXEC);
        }
+       /* Use module path for including Lua scripts */
+       engine_cmd(engine, "package.path = package.path..';" PREFIX MODULEDIR "/?.lua'");
 
        /* Load config file */
        int ret = 0;
@@ -314,11 +264,8 @@ static int engine_loadconf(struct engine *engine)
        if (ret != 0) {
                fprintf(stderr, "%s\n", lua_tostring(engine->L, -1));
                lua_pop(engine->L, 1);
-               return kr_error(EINVAL);
        }
-
-       /* Use module path for including Lua scripts */
-       return engine_cmd(engine, "package.path = '" PREFIX MODULEDIR "/?.lua;'..package.path");
+       return ret;
 }
 
 int engine_start(struct engine *engine)
@@ -337,41 +284,6 @@ void engine_stop(struct engine *engine)
        uv_stop(uv_default_loop());
 }
 
-/** @internal Helper macro for function presence check. */
-#define REGISTER_FFI_CALL(L, attr, name, cb) do { \
-       lua_getfield((L), -1, (name)); \
-       if (!lua_isnil((L), -1)) { attr = cb; } \
-       lua_pop((L), 1); \
-       } while (0)
-
-/** Register Lua module as a FFI module */
-static int register_lua_module(struct engine *engine, struct kr_module *module, const char *name)
-{
-       /* Register module in Lua */
-       lua_State *L = engine->L;
-       lua_getglobal(L, "require");
-       lua_pushstring(L, name);
-       if (engine_pcall(L, 1) != 0) {
-               lua_pop(L, 1);
-               return kr_error(ENOENT);
-       }
-       lua_setglobal(L, name);
-       lua_getglobal(L, name);
-
-       /* Create FFI module with trampolined functions. */
-       memset(module, 0, sizeof(*module));
-       module->name = strdup(name);
-       REGISTER_FFI_CALL(L, module->init,   "init",   &l_ffi_init);
-       REGISTER_FFI_CALL(L, module->deinit, "deinit", &l_ffi_deinit);
-       REGISTER_FFI_CALL(L, module->config, "config", &l_ffi_config);
-       REGISTER_FFI_CALL(L, module->layer,  "layer",  &l_ffi_layer);
-       module->lib = L;
-       module->data = (void *)(intptr_t)luaL_ref(L, LUA_REGISTRYINDEX);
-       return module->init(module);
-}
-
-#undef REGISTER_FFI_CALL
-
 /** Register module properties in Lua environment */
 static int register_properties(struct engine *engine, struct kr_module *module)
 {
@@ -411,7 +323,7 @@ int engine_register(struct engine *engine, const char *name)
        int ret = kr_module_load(module, name, NULL);
        /* Load Lua module if not a binary */
        if (ret == kr_error(ENOENT)) {
-               ret = register_lua_module(engine, module, name);
+               ret = ffimodule_register_lua(engine, module, name);
        }
        if (ret != 0) {
                return ret;
diff --git a/daemon/ffimodule.c b/daemon/ffimodule.c
new file mode 100644 (file)
index 0000000..ceeb0e9
--- /dev/null
@@ -0,0 +1,232 @@
+/*  Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <uv.h>
+
+#include "daemon/bindings.h"
+#include "daemon/ffimodule.h"
+#include "lib/module.h"
+#include "lib/layer.h"
+
+/** @internal Helper for retrieving the right function entrypoint. */
+static inline lua_State *l_ffi_preface(struct kr_module *module, const char *call) {
+       lua_State *L = module->lib;
+       lua_rawgeti(L, LUA_REGISTRYINDEX, (intptr_t)module->data);
+       lua_getfield(L, -1, call);
+       lua_pushlightuserdata(L, module);
+       return L;
+}
+
+/** @internal Helper for calling the entrypoint. */
+static inline int l_ffi_resume(lua_State *L, int argc)
+{
+#if LUA_VERSION_NUM >= 502
+       int status = lua_resume(L, NULL, argc);
+#else
+       int status = lua_resume(L, argc);
+#endif
+       switch(status) {
+       case LUA_YIELD: /* Continuation */
+               return kr_error(EAGAIN);
+       case 0: /* Finished */
+               return lua_tonumber(L, 1);
+       default: /* Error */
+               lua_pop(L, 1);
+               return kr_error(EIO);
+       }
+}
+
+/** @internal Continue with coroutine. */
+static void l_ffi_resume_cb(uv_idle_t *check)
+{
+       lua_State *L = check->data;
+       int status = l_ffi_resume(L, 0);
+       if (status != kr_error(EAGAIN)) {
+               uv_idle_stop(check); /* Stop coroutine */
+               uv_close((uv_handle_t *)check, (uv_close_cb)free);
+       }
+}
+
+/** @internal Schedule deferred continuation. */
+static int l_ffi_defer(lua_State *L)
+{
+       uv_idle_t *check = malloc(sizeof(*check));
+       if (!check) {
+               return kr_error(ENOMEM);
+       }
+       uv_idle_init(uv_default_loop(), check);
+       check->data = L;
+       return uv_idle_start(check, l_ffi_resume_cb);
+}
+
+/** @internal Helper for calling the entrypoint. */
+static inline int l_ffi_call(lua_State *L, int argc)
+{
+       int status = l_ffi_resume(L, argc);
+       if (status == kr_error(EAGAIN)) {
+               return l_ffi_defer(L);
+       }
+       return status;
+}
+
+static int l_ffi_init(struct kr_module *module)
+{
+       lua_State *L = l_ffi_preface(module, "init");
+       return l_ffi_call(L, 1);
+}
+
+static int l_ffi_deinit(struct kr_module *module)
+{
+       lua_State *L = l_ffi_preface(module, "deinit");
+       /* @note Do not allow coroutine here. */
+       int ret = l_ffi_resume(L, 1);
+       /* Free the layer API wrapper */
+       lua_rawgeti(L, LUA_REGISTRYINDEX, (intptr_t)module->data);
+       lua_getfield(L, -1, "_layercdata");
+       free(lua_touserdata(L, -1));
+       lua_pop(L, 1);
+       /* Unref module and unset 'lib', so the module
+        * interface doesn't attempt to close it.
+        */
+       lua_pushnil(L);
+       lua_setglobal(L, module->name);
+       luaL_unref(L, LUA_REGISTRYINDEX, (intptr_t)module->data);
+       module->lib = NULL;
+       return ret;
+}
+
+/** @internal Helper for retrieving layer Lua function by name. */
+#define LAYER_FFI_CALL(ctx, name) \
+       struct kr_module *module = (ctx)->api->data; \
+       lua_State *L = module->lib; \
+       lua_rawgeti(L, LUA_REGISTRYINDEX, (intptr_t)module->data); \
+       lua_getfield(L, -1, "layer"); \
+       lua_getfield(L, -1, (name)); \
+       if (lua_isnil(L, -1)) { \
+               return ctx->state; \
+       } \
+       lua_pushnumber(L, ctx->state);
+
+static int l_ffi_layer_begin(knot_layer_t *ctx, void *module_param)
+{
+       LAYER_FFI_CALL(ctx, "begin");
+       lua_pushlightuserdata(L, module_param);
+       ctx->data = module_param;
+       return l_ffi_resume(L, 2);
+}
+
+static int l_ffi_layer_reset(knot_layer_t *ctx)
+{
+       LAYER_FFI_CALL(ctx, "reset");
+       lua_pushlightuserdata(L, ctx->data);
+       return l_ffi_resume(L, 2);
+}
+
+static int l_ffi_layer_finish(knot_layer_t *ctx)
+{
+       LAYER_FFI_CALL(ctx, "finish");
+       lua_pushlightuserdata(L, ctx->data);
+       return l_ffi_resume(L, 2);
+}
+
+static int l_ffi_layer_consume(knot_layer_t *ctx, knot_pkt_t *pkt)
+{
+       LAYER_FFI_CALL(ctx, "consume");
+       lua_pushlightuserdata(L, ctx->data);
+       lua_pushlightuserdata(L, pkt);
+       return l_ffi_resume(L, 3);
+}
+
+static int l_ffi_layer_produce(knot_layer_t *ctx, knot_pkt_t *pkt)
+{
+       LAYER_FFI_CALL(ctx, "produce");
+       lua_pushlightuserdata(L, ctx->data);
+       lua_pushlightuserdata(L, pkt);
+       return l_ffi_resume(L, 3);
+}
+
+static int l_ffi_layer_fail(knot_layer_t *ctx, knot_pkt_t *pkt)
+{
+       LAYER_FFI_CALL(ctx, "fail");
+       lua_pushlightuserdata(L, ctx->data);
+       lua_pushlightuserdata(L, pkt);
+       return l_ffi_resume(L, 3);
+}
+
+/** @internal Retrieve C layer api wrapper. */
+static const knot_layer_api_t* l_ffi_layer(struct kr_module *module)
+{
+       lua_State *L = module->lib;
+       lua_rawgeti(L, LUA_REGISTRYINDEX, (intptr_t)module->data);
+       lua_getfield(L, -1, "_layer_capi");
+       if (lua_isnil(L, -1)) {
+               /* Fabricate layer API wrapping the Lua functions */
+               knot_layer_api_t *api = malloc(sizeof(*api));
+               if (!api) {
+                       return NULL;
+               }
+               api->begin = l_ffi_layer_begin;
+               api->finish = l_ffi_layer_finish;
+               api->consume = l_ffi_layer_consume;
+               api->produce = l_ffi_layer_produce;
+               api->reset = l_ffi_layer_reset;
+               api->fail = l_ffi_layer_fail;
+               api->data = module;
+               /* Store the api in the registry. */
+               lua_pop(L, 1);
+               lua_pushlightuserdata(L, api);
+               lua_setfield(L, -2, "_layer_capi");
+               return api;
+       } else {
+               return lua_touserdata(L, -1);
+       }
+}
+
+#undef LAYER_FFI_CALL
+
+/** @internal Helper macro for function presence check. */
+#define REGISTER_FFI_CALL(L, attr, name, cb) do { \
+       lua_getfield((L), -1, (name)); \
+       if (!lua_isnil((L), -1)) { attr = cb; } \
+       lua_pop((L), 1); \
+       } while (0)
+
+int ffimodule_register_lua(struct engine *engine, struct kr_module *module, const char *name)
+{
+       /* Register module in Lua */
+       lua_State *L = engine->L;
+       lua_getglobal(L, "require");
+       lua_pushstring(L, name);
+       if (lua_pcall(L, 1, LUA_MULTRET, 0) != 0) {
+               fprintf(stderr, "error: %s\n", lua_tostring(L, -1));
+               lua_pop(L, 1);
+               return kr_error(ENOENT);
+       }
+       lua_setglobal(L, name);
+       lua_getglobal(L, name);
+
+       /* Create FFI module with trampolined functions. */
+       memset(module, 0, sizeof(*module));
+       module->name = strdup(name);
+       REGISTER_FFI_CALL(L, module->init,   "init",   &l_ffi_init);
+       REGISTER_FFI_CALL(L, module->deinit, "deinit", &l_ffi_deinit);
+       REGISTER_FFI_CALL(L, module->layer,  "layer",  &l_ffi_layer);
+       module->data = (void *)(intptr_t)luaL_ref(L, LUA_REGISTRYINDEX);
+       module->lib = lua_newthread(L);
+       return module->init(module);
+}
+
+#undef REGISTER_FFI_CALL
\ No newline at end of file
diff --git a/daemon/ffimodule.h b/daemon/ffimodule.h
new file mode 100644 (file)
index 0000000..6a8b185
--- /dev/null
@@ -0,0 +1,33 @@
+/*  Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+/**
+ * Register Lua module as a FFI module.
+ * This fabricates a standard module interface,
+ * that trampolines to the Lua module methods.
+ *
+ * @note Lua module is loaded in it's own coroutine,
+ *       so it's possible to yield and resume at arbitrary
+ *       places except deinit()
+ * 
+ * @param  engine daemon engine
+ * @param  module prepared module
+ * @param  name   module name
+ * @return        0 or an error
+ */
+int ffimodule_register_lua(struct engine *engine, struct kr_module *module, const char *name);
\ No newline at end of file