[Tarantool-patches] [PATCH v5 5/6] box/cbox: implement cbox Lua module

Cyrill Gorcunov gorcunov at gmail.com
Fri Oct 9 00:36:07 MSK 2020


Currently to run "C" function from some external module
one have to register it first in "_func" system space. This
is a problem if node is in read-only mode (replica).

Still people would like to have a way to run such functions
even in ro mode. For this sake we implement "cbox" lua module.

Fixes #4692

Signed-off-by: Cyrill Gorcunov <gorcunov at gmail.com>

@TarantoolBot document
Title: cbox module

Overview
========

`cbox` module provides a way to create, delete and execute
`C` procedures. Unlinke `box.schema.func` functionality this
functions are not persistent in any space and live purely in
memory, thus once node get turned off they are vanished. Same
time such functions are allowed to be created and executed
when a node is in read-only mode.

Module functions
================

`cbox.func.create(name) -> obj | nil, err`
------------------------------------------

Create a new function with name `name`.

Possible errors:
 - IllegalParams: function name is either not supplied
   or not a string.
 - IllegalParams: a function is already created.
 - OutOfMemory: unable to allocate a function.

On success the new callable object is returned, otherwise
`nil,error` pair.

Example:

``` Lua
f, err = require('cbox').create('func')
if not f then
    print(err)
end
-- call the function
f(arg1,arg2,...)
```

`cbox.func.drop(name) -> true | nil, err`
-----------------------------------------

Deletes a function with name `name`.

Possible errors:
 - IllegalParams: function name is either not supplied
   or not a string.
 - IllegalParams: the function does not exist.

On success `true` is returned, otherwise `nil,error` pair.

Example:

``` Lua
ok, err = require('cbox').func.drop('func')
if not ok then
    print(err)
end
```

`cbox.module.reload(name) -> true | nil, err`
---------------------------------------------

Reloads a module with name `name` and all functions which
were associated for the module.

Possible errors:
 - IllegalParams: module name is either not supplied
   or not a string.
 - IllegalParams: the function does not exist.
 - ClientError: a module with the name provided does
   not exist.

On success `true` is returned, otherwise `nil,error` pair.

Example:

``` Lua
ok, err = require('cbox').module.reload('func')
if not ok then
    print(err)
end
```
---
 src/box/CMakeLists.txt |   1 +
 src/box/box.cc         |   4 +
 src/box/lua/cbox.c     | 417 +++++++++++++++++++++++++++++++++++++++++
 src/box/lua/cbox.h     |  39 ++++
 src/box/lua/init.c     |   2 +
 5 files changed, 463 insertions(+)
 create mode 100644 src/box/lua/cbox.c
 create mode 100644 src/box/lua/cbox.h

diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index 8b2e704cf..358b0ad39 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -196,6 +196,7 @@ add_library(box STATIC
     lua/init.c
     lua/call.c
     lua/cfg.cc
+    lua/cbox.c
     lua/console.c
     lua/serialize_lua.c
     lua/tuple.c
diff --git a/src/box/box.cc b/src/box/box.cc
index 6ec813c12..512b22c2f 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -75,6 +75,7 @@
 #include "systemd.h"
 #include "call.h"
 #include "func.h"
+#include "lua/cbox.h"
 #include "sequence.h"
 #include "sql_stmt_cache.h"
 #include "msgpack.h"
@@ -2647,6 +2648,9 @@ box_init(void)
 	if (module_init() != 0)
 		diag_raise();
 
+	if (cbox_init() != 0)
+		diag_raise();
+
 	if (tuple_init(lua_hash) != 0)
 		diag_raise();
 
diff --git a/src/box/lua/cbox.c b/src/box/lua/cbox.c
new file mode 100644
index 000000000..58585b44d
--- /dev/null
+++ b/src/box/lua/cbox.c
@@ -0,0 +1,417 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2010-2020, Tarantool AUTHORS, please see AUTHORS file.
+ */
+
+#include <string.h>
+#include <lua.h>
+
+#include "tt_static.h"
+#include "assoc.h"
+#include "diag.h"
+#include "port.h"
+#include "say.h"
+
+#include "box/error.h"
+#include "box/func.h"
+#include "box/call.h"
+#include "box/port.h"
+
+#include "trivia/util.h"
+#include "lua/utils.h"
+
+/** Function names to descriptor map. */
+static struct mh_strnptr_t *func_hash;
+
+/**
+ * Function descriptor.
+ */
+struct cbox_func {
+	/**
+	 * Symbol descriptor for the function in
+	 * an associated module.
+	 */
+	struct module_sym mod_sym;
+
+	/** Function name length */
+	size_t name_len;
+
+	/** Function name */
+	char name[0];
+};
+
+/**
+ * Find function descriptor by its name.
+ */
+struct cbox_func *
+cbox_func_find(const char *name, size_t len)
+{
+	if (name != NULL && len > 0) {
+		mh_int_t e = mh_strnptr_find_inp(func_hash, name, len);
+		if (e != mh_end(func_hash))
+			return mh_strnptr_node(func_hash, e)->val;
+	}
+	return NULL;
+}
+
+/**
+ * Add function descriptor to the cache.
+ */
+static int
+cbox_func_add(struct cbox_func *cfunc)
+{
+	const struct mh_strnptr_node_t nd = {
+		.str	= cfunc->name,
+		.len	= cfunc->name_len,
+		.hash	= mh_strn_hash(cfunc->name, cfunc->name_len),
+		.val	= cfunc,
+	};
+
+	mh_int_t h = mh_strnptr_put(func_hash, &nd, NULL, NULL);
+	if (h == mh_end(func_hash)) {
+		diag_set(OutOfMemory, sizeof(nd), __func__, "h");
+		return -1;
+	}
+	return 0;
+}
+
+/**
+ * Setup a detailed error description into the diagnostics area.
+ */
+static void
+diag_set_param_error(struct lua_State *L, int idx, const char *func_name,
+		     const char *param, const char *exp)
+{
+	const char *typename = idx == 0 ?
+		"<unknown>" : lua_typename(L, lua_type(L, idx));
+	static const char *fmt =
+		"%s: wrong parameter \"%s\": expected %s, got %s";
+	diag_set(IllegalParams, fmt, func_name, param, exp, typename);
+}
+
+/**
+ * Call function by its name from the Lua code.
+ */
+static int
+lcbox_func_call(struct lua_State *L)
+{
+	int nr_args = lua_gettop(L);
+
+	/*
+	 * A function name is associated with the variable.
+	 */
+	lua_getfield(L, -nr_args, "name");
+	size_t name_len;
+	const char *name = lua_tolstring(L, -1, &name_len);
+	struct cbox_func *cf = cbox_func_find(name, name_len);
+	/*
+	 * Do not pop the result early, since Lua's GC may
+	 * drop the name string found.
+	 */
+	lua_pop(L, 1);
+
+	if (cf == NULL) {
+		/*
+		 * This can happen if someone changed the
+		 * name of the function manually.
+		 */
+		const char *fmt = tnt_errcode_desc(ER_FUNCTION_EXISTS);
+		diag_set(IllegalParams, fmt, name);
+		return luaT_push_nil_and_error(L);
+	}
+
+	/*
+	 * FIXME: We should get rid of luaT_newthread but this
+	 * requires serious modifucations. In particular
+	 * port_lua_do_dump uses tarantool_L reference and
+	 * coro_ref must be valid as well.
+	 */
+	lua_State *args_L = luaT_newthread(tarantool_L);
+	if (args_L == NULL)
+		return luaT_push_nil_and_error(L);
+
+	int coro_ref = luaL_ref(tarantool_L, LUA_REGISTRYINDEX);
+	lua_xmove(L, args_L, lua_gettop(L) - 1);
+
+	struct port args;
+	port_lua_create(&args, args_L);
+	((struct port_lua *)&args)->ref = coro_ref;
+
+	struct port ret;
+	if (module_sym_call(&cf->mod_sym, &args, &ret) != 0) {
+		port_destroy(&args);
+		return luaT_push_nil_and_error(L);
+	}
+
+	int top = lua_gettop(L);
+	lua_pushboolean(L, true);
+	port_dump_lua(&ret, L, true);
+	int cnt = lua_gettop(L) - top;
+
+	port_destroy(&ret);
+	port_destroy(&args);
+
+	return cnt;
+}
+
+/**
+ * Create a new function.
+ *
+ * This function takes a function name from the caller
+ * stack @a L and creates a new function object.
+ *
+ * Possible errors:
+ *
+ * - IllegalParams: function name is either not supplied
+ *   or not a string.
+ * - IllegalParams: a function is already created.
+ * - OutOfMemory: unable to allocate a function.
+ *
+ * @returns function object on success or {nil,error} on error,
+ * the error is set to the diagnostics area.
+ */
+static int
+lcbox_func_create(struct lua_State *L)
+{
+	const char *method = "cbox.func.create";
+	struct cbox_func *cf = NULL;
+
+	if (lua_gettop(L) != 1) {
+		const char *fmt = "Expects %s(\'name\')";
+		diag_set(IllegalParams, fmt, method);
+		goto out;
+	}
+
+	if (lua_type(L, 1) != LUA_TSTRING) {
+		diag_set_param_error(L, 1, method,
+				     lua_tostring(L, 1),
+				     "function name");
+		goto out;
+	}
+
+	size_t name_len;
+	const char *name = lua_tolstring(L, 1, &name_len);
+
+	if (cbox_func_find(name, name_len) != NULL) {
+		const char *fmt = tnt_errcode_desc(ER_FUNCTION_EXISTS);
+		diag_set(IllegalParams, fmt, name);
+		goto out;
+	}
+
+	size_t size = sizeof(*cf) + name_len + 1;
+	cf = malloc(size);
+	if (cf == NULL) {
+		diag_set(OutOfMemory, size, "malloc", "cf");
+		goto out;
+	}
+
+	cf->mod_sym.addr	= NULL;
+	cf->mod_sym.module	= NULL;
+	cf->mod_sym.name	= cf->name;
+	cf->name_len		= name_len;
+
+	memcpy(cf->name, name, name_len);
+	cf->name[name_len] = '\0';
+
+	if (cbox_func_add(cf) != 0)
+		goto out;
+
+	/*
+	 * A new variable should be callable for
+	 * convenient use in Lua.
+	 */
+	lua_newtable(L);
+
+	lua_pushstring(L, "name");
+	lua_pushstring(L, cf->name);
+	lua_settable(L, -3);
+
+	lua_newtable(L);
+
+	lua_pushstring(L, "__call");
+	lua_pushcfunction(L, lcbox_func_call);
+	lua_settable(L, -3);
+
+	lua_setmetatable(L, -2);
+	return 1;
+
+out:
+	free(cf);
+	return luaT_push_nil_and_error(L);
+}
+
+/**
+ * Drop a function.
+ *
+ * This function takes a function name from the caller
+ * stack @a L and drops a function object.
+ *
+ * Possible errors:
+ *
+ * - IllegalParams: function name is either not supplied
+ *   or not a string.
+ * - IllegalParams: the function does not exist.
+ *
+ * @returns true on success or {nil,error} on error,
+ * the error is set to the diagnostics area.
+ */
+static int
+lcbox_func_drop(struct lua_State *L)
+{
+	const char *method = "cbox.func.drop";
+	const char *name = NULL;
+
+	if (lua_gettop(L) != 1) {
+		const char *fmt = "expects %s(\'name\')";
+		diag_set(IllegalParams, fmt, method);
+		return luaT_push_nil_and_error(L);
+	}
+
+	if (lua_type(L, 1) != LUA_TSTRING) {
+		diag_set_param_error(L, 1, method,
+				     lua_tostring(L, 1),
+				     "function name or id");
+		return luaT_push_nil_and_error(L);
+	}
+
+	size_t name_len;
+	name = lua_tolstring(L, 1, &name_len);
+
+	mh_int_t e = mh_strnptr_find_inp(func_hash, name, name_len);
+	if (e == mh_end(func_hash)) {
+		const char *fmt = tnt_errcode_desc(ER_NO_SUCH_FUNCTION);
+		diag_set(IllegalParams, fmt, name);
+		return luaT_push_nil_and_error(L);
+	}
+
+	struct cbox_func *cf = mh_strnptr_node(func_hash, e)->val;
+	mh_strnptr_del(func_hash, e, NULL);
+	free(cf);
+
+	lua_pushboolean(L, true);
+	return 1;
+}
+
+/**
+ * Reload a module.
+ *
+ * This function takes a module name from the caller
+ * stack @a L and reloads all functions associated with
+ * the module.
+ *
+ * Possible errors:
+ *
+ * - IllegalParams: module name is either not supplied
+ *   or not a string.
+ * - IllegalParams: the function does not exist.
+ * - ClientError: a module with the name provided does
+ *   not exist.
+ *
+ * @returns true on success or {nil,error} on error,
+ * the error is set to the diagnostics area.
+ */
+static int
+lcbox_module_reload(struct lua_State *L)
+{
+	const char *method = "cbox.module.reload";
+	const char *fmt = "Expects %s(\'name\') but no name passed";
+
+	if (lua_gettop(L) != 1 || !lua_isstring(L, 1)) {
+		diag_set(IllegalParams, fmt, method);
+		return luaT_push_nil_and_error(L);
+	}
+
+	size_t name_len;
+	const char *name = lua_tolstring(L, 1, &name_len);
+	if (name == NULL || name_len < 1) {
+		diag_set(IllegalParams, fmt, method);
+		return luaT_push_nil_and_error(L);
+	}
+
+	struct module *module = NULL;
+	if (module_reload(name, &name[name_len], &module) == 0) {
+		if (module != NULL) {
+			lua_pushboolean(L, true);
+			return 1;
+		}
+		diag_set(ClientError, ER_NO_SUCH_MODULE, name);
+	}
+	return luaT_push_nil_and_error(L);
+}
+
+/**
+ * Initialize cbox Lua module.
+ *
+ * @param L Lua state where to register the cbox module.
+ */
+void
+box_lua_cbox_init(struct lua_State *L)
+{
+	static const struct luaL_Reg cbox_methods[] = {
+		{ NULL, NULL }
+	};
+	luaL_register_module(L, "cbox", cbox_methods);
+
+	/* func table */
+	static const struct luaL_Reg func_table[] = {
+		{ "create",	lcbox_func_create },
+		{ "drop",	lcbox_func_drop },
+	};
+
+	lua_newtable(L);
+	for (size_t i = 0; i < lengthof(func_table); i++) {
+		lua_pushstring(L, func_table[i].name);
+		lua_pushcfunction(L, func_table[i].func);
+		lua_settable(L, -3);
+	}
+	lua_setfield(L, -2, "func");
+
+	/* module table */
+	static const struct luaL_Reg module_table[] = {
+		{ "reload",	lcbox_module_reload },
+	};
+
+	lua_newtable(L);
+	for (size_t i = 0; i < lengthof(module_table); i++) {
+		lua_pushstring(L, module_table[i].name);
+		lua_pushcfunction(L, module_table[i].func);
+		lua_settable(L, -3);
+	}
+	lua_setfield(L, -2, "module");
+
+	lua_pop(L, 1);
+}
+
+/**
+ * Initialize cbox module.
+ *
+ * @return 0 on success, -1 on error (diag is set).	
+ */
+int
+cbox_init(void)
+{
+	func_hash = mh_strnptr_new();
+	if (func_hash == NULL) {
+		diag_set(OutOfMemory, sizeof(*func_hash), "malloc",
+			  "cbox.func hash table");
+		return -1;
+	}
+	return 0;
+}
+
+/**
+ * Free cbox module.
+ */
+void
+cbox_free(void)
+{
+	while (mh_size(func_hash) > 0) {
+		mh_int_t e = mh_first(func_hash);
+		struct cbox_func *cf = mh_strnptr_node(func_hash, e)->val;
+		module_sym_unload(&cf->mod_sym);
+		mh_strnptr_del(func_hash, e, NULL);
+	}
+	mh_strnptr_delete(func_hash);
+	func_hash = NULL;
+}
diff --git a/src/box/lua/cbox.h b/src/box/lua/cbox.h
new file mode 100644
index 000000000..1e6cd9a9b
--- /dev/null
+++ b/src/box/lua/cbox.h
@@ -0,0 +1,39 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2010-2020, Tarantool AUTHORS, please see AUTHORS file.
+ */
+
+#pragma once
+
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+struct lua_State;
+
+/**
+ * Initialize cbox Lua module.
+ *
+ * @param L Lua state where to register the cbox module.
+ */
+void
+box_lua_cbox_init(struct lua_State *L);
+
+/**
+ * Initialize cbox module.
+ *
+ * @return 0 on success, -1 on error (diag is set).	
+ */
+int
+cbox_init(void);
+
+/**
+ * Free cbox module.
+ */
+void
+cbox_free(void);
+
+#if defined(__cplusplus)
+}
+#endif /* defined(__plusplus) */
diff --git a/src/box/lua/init.c b/src/box/lua/init.c
index d0316ef86..b37aa284a 100644
--- a/src/box/lua/init.c
+++ b/src/box/lua/init.c
@@ -61,6 +61,7 @@
 #include "box/lua/cfg.h"
 #include "box/lua/xlog.h"
 #include "box/lua/console.h"
+#include "box/lua/cbox.h"
 #include "box/lua/tuple.h"
 #include "box/lua/execute.h"
 #include "box/lua/key_def.h"
@@ -466,6 +467,7 @@ box_lua_init(struct lua_State *L)
 	box_lua_tuple_init(L);
 	box_lua_call_init(L);
 	box_lua_cfg_init(L);
+	box_lua_cbox_init(L);
 	box_lua_slab_init(L);
 	box_lua_index_init(L);
 	box_lua_space_init(L);
-- 
2.26.2



More information about the Tarantool-patches mailing list