Commit a03a79e3 authored by Marek Vavruša's avatar Marek Vavruša

daemon/ffimodule: support for Lua modules (including layers)

parent 65ddccc0
......@@ -4,6 +4,7 @@ kresolved_SOURCES := \
daemon/engine.c \
daemon/worker.c \
daemon/bindings.c \
daemon/ffimodule.c \
daemon/main.c
# Embed resources
......
......@@ -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;
......
/* 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
/* 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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment