From a03a79e3f33eb369cd26d2e624c62d6540f4311c Mon Sep 17 00:00:00 2001 From: =?utf8?q?Marek=20Vavru=C5=A1a?= Date: Thu, 7 May 2015 10:35:53 +0200 Subject: [PATCH] daemon/ffimodule: support for Lua modules (including layers) --- daemon/daemon.mk | 1 + daemon/engine.c | 98 +------------------ daemon/ffimodule.c | 232 +++++++++++++++++++++++++++++++++++++++++++++ daemon/ffimodule.h | 33 +++++++ 4 files changed, 271 insertions(+), 93 deletions(-) create mode 100644 daemon/ffimodule.c create mode 100644 daemon/ffimodule.h diff --git a/daemon/daemon.mk b/daemon/daemon.mk index a7e52a30d..6cfc44db7 100644 --- a/daemon/daemon.mk +++ b/daemon/daemon.mk @@ -4,6 +4,7 @@ kresolved_SOURCES := \ daemon/engine.c \ daemon/worker.c \ daemon/bindings.c \ + daemon/ffimodule.c \ daemon/main.c # Embed resources diff --git a/daemon/engine.c b/daemon/engine.c index 8507b0849..d1fc3a325 100644 --- a/daemon/engine.c +++ b/daemon/engine.c @@ -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 index 000000000..ceeb0e94b --- /dev/null +++ b/daemon/ffimodule.c @@ -0,0 +1,232 @@ +/* Copyright (C) 2015 CZ.NIC, z.s.p.o. + + 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 . + */ + +#include + +#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 index 000000000..6a8b1854c --- /dev/null +++ b/daemon/ffimodule.h @@ -0,0 +1,33 @@ +/* Copyright (C) 2015 CZ.NIC, z.s.p.o. + + 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 . + */ + +#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 -- 2.47.2