summaryrefslogtreecommitdiffstats
path: root/daemon/ffimodule.c
diff options
context:
space:
mode:
Diffstat (limited to 'daemon/ffimodule.c')
-rw-r--r--daemon/ffimodule.c289
1 files changed, 289 insertions, 0 deletions
diff --git a/daemon/ffimodule.c b/daemon/ffimodule.c
new file mode 100644
index 0000000..1cb6b71
--- /dev/null
+++ b/daemon/ffimodule.c
@@ -0,0 +1,289 @@
+/* Copyright (C) 2015-2017 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <uv.h>
+
+#include "daemon/engine.h"
+#include "daemon/ffimodule.h"
+#include "daemon/bindings.h"
+#include "lib/module.h"
+#include "lib/layer.h"
+
+#if LUA_VERSION_NUM >= 502
+#define l_resume(L, argc) lua_resume((L), NULL, (argc))
+#else
+#define l_resume(L, argc) lua_resume((L), (argc))
+#endif
+
+/** @internal Slots for layer callbacks.
+ * Each slot ID corresponds to Lua reference in module API. */
+enum {
+ SLOT_begin = 0,
+ SLOT_reset,
+ SLOT_finish,
+ SLOT_consume,
+ SLOT_produce,
+ SLOT_checkout,
+ SLOT_answer_finalize,
+ SLOT_count /* dummy, must be the last */
+};
+#define SLOT_size sizeof(int)
+
+/** @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_getglobal(L, module->name);
+ lua_getfield(L, -1, call);
+ lua_remove(L, -2);
+ if (lua_isnil(L, -1)) {
+ lua_pop(L, 1);
+ return NULL;
+ }
+ lua_pushlightuserdata(L, module);
+ return L;
+}
+
+/** @internal Continue with coroutine. */
+static void l_ffi_resume_cb(uv_idle_t *check)
+{
+ lua_State *L = check->data;
+ int status = l_resume(L, 0);
+ if (status != LUA_YIELD) {
+ uv_idle_stop(check); /* Stop coroutine */
+ uv_close((uv_handle_t *)check, (uv_close_cb)free);
+ }
+ lua_pop(L, lua_gettop(L));
+}
+
+/** @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 = lua_pcall(L, argc, 1, 0);
+ if (status != 0) {
+ fprintf(stderr, "error: %s\n", lua_tostring(L, -1));
+ lua_pop(L, 1);
+ return kr_error(EIO);
+ }
+ if (lua_isnumber(L, -1)) { /* Return code */
+ status = lua_tonumber(L, -1);
+ } else if (lua_isthread(L, -1)) { /* Continuations */
+ status = l_ffi_defer(lua_tothread(L, -1));
+ }
+ lua_pop(L, 1);
+ return status;
+}
+
+static int l_ffi_init(struct kr_module *module)
+{
+ lua_State *L = l_ffi_preface(module, "init");
+ if (!L) {
+ return 0;
+ }
+ return l_ffi_call(L, 1);
+}
+
+static int l_ffi_deinit(struct kr_module *module)
+{
+ /* Deinit the module in Lua (if possible) */
+ int ret = 0;
+ lua_State *L = module->lib;
+ if (l_ffi_preface(module, "deinit")) {
+ ret = l_ffi_call(L, 1);
+ }
+ module->lib = NULL;
+ /* Free the layer API wrapper (unconst it) */
+ kr_layer_api_t* api = module->data;
+ if (!api) {
+ return ret;
+ }
+ /* Unregister layer callback references from registry. */
+ for (int si = 0; si < SLOT_count; ++si) {
+ if (api->cb_slots[si] > 0) {
+ luaL_unref(L, LUA_REGISTRYINDEX, api->cb_slots[si]);
+ }
+ }
+ free(api);
+ return ret;
+}
+
+/** @internal Helper for retrieving layer Lua function by name. */
+#define LAYER_FFI_CALL(ctx, slot_name) \
+ const int *cb_slot = (ctx)->api->cb_slots + SLOT_ ## slot_name; \
+ if (*cb_slot <= 0) { \
+ return ctx->state; \
+ } \
+ struct kr_module *module = (ctx)->api->data; \
+ lua_State *L = module->lib; \
+ lua_rawgeti(L, LUA_REGISTRYINDEX, *cb_slot); \
+ lua_pushnumber(L, ctx->state)
+
+static int l_ffi_layer_begin(kr_layer_t *ctx)
+{
+ LAYER_FFI_CALL(ctx, begin);
+ lua_pushlightuserdata(L, ctx->req);
+ return l_ffi_call(L, 2);
+}
+
+static int l_ffi_layer_reset(kr_layer_t *ctx)
+{
+ LAYER_FFI_CALL(ctx, reset);
+ lua_pushlightuserdata(L, ctx->req);
+ return l_ffi_call(L, 2);
+}
+
+static int l_ffi_layer_finish(kr_layer_t *ctx)
+{
+ struct kr_request *req = ctx->req;
+ LAYER_FFI_CALL(ctx, finish);
+ lua_pushlightuserdata(L, req);
+ lua_pushlightuserdata(L, req->answer);
+ return l_ffi_call(L, 3);
+}
+
+static int l_ffi_layer_consume(kr_layer_t *ctx, knot_pkt_t *pkt)
+{
+ if (ctx->state & KR_STATE_FAIL) {
+ return ctx->state; /* Already failed, skip */
+ }
+ LAYER_FFI_CALL(ctx, consume);
+ lua_pushlightuserdata(L, ctx->req);
+ lua_pushlightuserdata(L, pkt);
+ return l_ffi_call(L, 3);
+}
+
+static int l_ffi_layer_produce(kr_layer_t *ctx, knot_pkt_t *pkt)
+{
+ if (ctx->state & (KR_STATE_FAIL)) {
+ return ctx->state; /* Already failed or done, skip */
+ }
+ LAYER_FFI_CALL(ctx, produce);
+ lua_pushlightuserdata(L, ctx->req);
+ lua_pushlightuserdata(L, pkt);
+ return l_ffi_call(L, 3);
+}
+
+static int l_ffi_layer_checkout(kr_layer_t *ctx, knot_pkt_t *pkt, struct sockaddr *dst, int type)
+{
+ if (ctx->state & (KR_STATE_FAIL)) {
+ return ctx->state; /* Already failed or done, skip */
+ }
+ LAYER_FFI_CALL(ctx, checkout);
+ lua_pushlightuserdata(L, ctx->req);
+ lua_pushlightuserdata(L, pkt);
+ lua_pushlightuserdata(L, dst);
+ lua_pushboolean(L, type == SOCK_STREAM);
+ return l_ffi_call(L, 5);
+}
+
+static int l_ffi_layer_answer_finalize(kr_layer_t *ctx)
+{
+ LAYER_FFI_CALL(ctx, answer_finalize);
+ lua_pushlightuserdata(L, ctx->req);
+ return l_ffi_call(L, 2);
+}
+#undef LAYER_FFI_CALL
+
+/** @internal Conditionally register layer trampoline
+ * @warning Expects 'module.layer' to be on top of Lua stack. */
+#define LAYER_REGISTER(L, api, name) do { \
+ int *cb_slot = (api)->cb_slots + SLOT_ ## name; \
+ lua_getfield((L), -1, #name); \
+ if (!lua_isnil((L), -1)) { \
+ (api)->name = l_ffi_layer_ ## name; \
+ *cb_slot = luaL_ref((L), LUA_REGISTRYINDEX); \
+ } else { \
+ lua_pop((L), 1); \
+ } \
+} while(0)
+
+/** @internal Create C layer api wrapper. */
+static kr_layer_api_t *l_ffi_layer_create(lua_State *L, struct kr_module *module)
+{
+ /* Fabricate layer API wrapping the Lua functions
+ * reserve slots after it for references to Lua callbacks. */
+ const size_t api_length = offsetof(kr_layer_api_t, cb_slots)
+ + (SLOT_count * SLOT_size);
+ kr_layer_api_t *api = malloc(api_length);
+ if (api) {
+ memset(api, 0, api_length);
+ LAYER_REGISTER(L, api, begin);
+ LAYER_REGISTER(L, api, finish);
+ LAYER_REGISTER(L, api, consume);
+ LAYER_REGISTER(L, api, produce);
+ LAYER_REGISTER(L, api, checkout);
+ LAYER_REGISTER(L, api, answer_finalize);
+ LAYER_REGISTER(L, api, reset);
+ /* Begin is always set, as it initializes layer baton. */
+ api->begin = l_ffi_layer_begin;
+ api->data = 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)
+{
+ /* 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);
+ module->init = &l_ffi_init;
+ module->deinit = &l_ffi_deinit;
+ /* 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->lib = L;
+ lua_pop(L, 2); /* Clear the layer + module global */
+ if (module->init) {
+ return module->init(module);
+ }
+ return kr_ok();
+}