[PATCH v2 9/9] box: export _func functions with box.func folder

Kirill Shcherbatov kshcherbatov at tarantool.org
Thu Jun 6 15:04:05 MSK 2019


Closes #4182
Needed for #1260

@TarantoolBot document
Title: New function machinery

This patchset introduces a set of changes in Tarantool function
machinery.
1. At first, the format of _func space is changed. A new space
format is
[<id> UINT, <owner> UINT, <name> STR, <setuid> UINT,
 <language> STR, <body> STR, <returns> STR,
 <is_deterministic> BOOL, <opts> MAP]

and box.schema.func.create endpoint is changed correspondingly:

box.schema.func.create('funcname', <setuid = true|FALSE>,
		<if_not_exists = true|FALSE>, <language = LUA|c>,
		<body = string ('')>, <returns = string (ANY)>,
		<is_deterministic = true|FALSE>,
		<opts = { <is_sandboxed = true|FALSE>} >)

2. Now all registered with box.schema.func.create functions are
exported in box.func folder.
Each function have :call and :drop method. The :drop method
just a shortcut for legacy box.schema.func.drop interface.
The :call method is similar to net.box connection:call method
except the format or returned value: the func:call method
returns a table of values like space:select does.

3. This patchset also introduces 'persistent' Lua functions.
Such functions are stored in snapshoot and are available after
restart.
To create a persistent Lua function, specify function body
in box.schema.func.create call:
e.g. body = "function(a, b) return a + b end"

4. A Lua persistent function may be 'sandboxed'. The 'sandboxed'
function executed in isolated environment:
  a. only limited set of Lua functions and modules are available:
    -assert -error -pairs -ipairs -next -pcall -xpcall -type
    -print -select -string -tonumber -tostring -unpack -math -utf8;
  b. global variables are forbidden

Example:
lua_code = [[function(a, b) return a + b end]]
box.schema.func.create('sum', {body = lua_code,
		is_deterministic = true, returns = 'integer',
		opts = {is_sandboxed = true}})
tarantool> box.func.sum
---
- is_sandboxed: true
  returns: integer
  is_deterministic: true
  id: 2
  language: LUA
  name: sum
  is_persistent: true
  setuid: false
...
box.func.sum:call({1, 2})
---
- - 3
...
conn:call("sum", {1, 3})
---
- 3
...
---
 src/box/alter.cc                  |   6 +-
 src/box/func.c                    | 138 ++++++++++++++++++++++++++++++
 src/box/func.h                    |   5 ++
 src/box/lua/call.c                |  22 +++++
 src/box/lua/init.c                |   2 +
 src/box/lua/schema.lua            |  25 ++++++
 src/box/schema.cc                 |   1 +
 src/box/schema.h                  |  16 ++--
 test/box/misc.result              |   1 +
 test/box/persistent_func.result   |  85 ++++++++++++++++++
 test/box/persistent_func.test.lua |  36 ++++++++
 11 files changed, 331 insertions(+), 6 deletions(-)

diff --git a/src/box/alter.cc b/src/box/alter.cc
index c769e4f3d..79477aabf 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -2517,7 +2517,9 @@ static void
 func_cache_remove_func(struct trigger *trigger, void * /* event */)
 {
 	struct func *old_func = (struct func *) trigger->data;
-	func_cache_delete(old_func->def->fid);
+	uint32_t fid = old_func->def->fid;
+	func_cache_delete(fid);
+	trigger_run_xc(&on_alter_func, (void *)(uintptr_t)fid);
 	func_delete(old_func);
 }
 
@@ -2530,6 +2532,7 @@ func_cache_replace_func(struct trigger *trigger, void * /* event */)
 	func_cache_replace(new_func, &old_func);
 	assert(old_func != NULL);
 	func_delete(old_func);
+	trigger_run_xc(&on_alter_func, (void *)(uintptr_t)new_func->def->fid);
 }
 
 /**
@@ -2562,6 +2565,7 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event)
 		func_cache_replace(func, &old_func);
 		assert(old_func == NULL);
 		func_guard.is_active = false;
+		trigger_run_xc(&on_alter_func, (void *)(uintptr_t)fid);
 		struct trigger *on_rollback =
 			txn_alter_trigger_new(func_cache_remove_func, func);
 		txn_on_rollback(txn, on_rollback);
diff --git a/src/box/func.c b/src/box/func.c
index f2065422c..fee7a6aed 100644
--- a/src/box/func.c
+++ b/src/box/func.c
@@ -28,11 +28,13 @@
  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
+#include "fiber.h"
 #include "func.h"
 #include "trivia/config.h"
 #include "assoc.h"
 #include "session.h"
 #include "lua/msgpack.h"
+#include "lua/trigger.h"
 #include "lua/utils.h"
 #include "schema.h"
 #include "txn.h"
@@ -830,3 +832,139 @@ box_func_execute_by_name(const char *name, uint32_t name_len, struct port *port,
 	assert(rc == 0 || diag_last_error(&fiber()->diag) != NULL);
 	return rc;
 }
+
+static void
+lbox_func_new(struct lua_State *L, struct func *func)
+{
+	lua_getfield(L, LUA_GLOBALSINDEX, "box");
+	lua_getfield(L, -1, "func");
+	if (!lua_istable(L, -1)) {
+		lua_pop(L, 1); /* pop nil */
+		lua_newtable(L);
+		lua_setfield(L, -2, "func");
+		lua_getfield(L, -1, "func");
+	}
+	lua_rawgeti(L, -1, func->def->fid);
+	if (lua_isnil(L, -1)) {
+		/*
+		 * If the function already exists, modify it,
+		 * rather than create a new one -- to not
+		 * invalidate Lua variable references to old func
+		 * outside the box.schema.func[].
+		 */
+		lua_pop(L, 1);
+		lua_newtable(L);
+		lua_rawseti(L, -2, func->def->fid);
+		lua_rawgeti(L, -1, func->def->fid);
+	} else {
+		/* Clear the reference to old space by old name. */
+		lua_getfield(L, -1, "name");
+		lua_pushnil(L);
+		lua_settable(L, -4);
+	}
+
+	int top = lua_gettop(L);
+	lua_pushstring(L, "id");
+	lua_pushnumber(L, func->def->fid);
+	lua_settable(L, top);
+
+	lua_pushstring(L, "name");
+	lua_pushstring(L, func->def->name);
+	lua_settable(L, top);
+
+	lua_pushstring(L, "is_persistent");
+	lua_pushboolean(L, func_def_is_persistent(func->def));
+	lua_settable(L, top);
+
+	lua_pushstring(L, "setuid");
+	lua_pushboolean(L, func->def->setuid);
+	lua_settable(L, top);
+
+	lua_pushstring(L, "is_deterministic");
+	lua_pushboolean(L, func->def->is_deterministic);
+	lua_settable(L, top);
+
+	lua_pushstring(L, "language");
+	lua_pushstring(L, func_language_strs[func->def->language]);
+	lua_settable(L, top);
+
+	lua_pushstring(L, "returns");
+	lua_pushstring(L, field_type_strs[func->def->returns]);
+	lua_settable(L, top);
+
+	if (func->def->opts.is_sandboxed) {
+		lua_pushstring(L, "is_sandboxed");
+		lua_pushboolean(L, func->def->opts.is_sandboxed);
+		lua_settable(L, top);
+	}
+
+	/* Bless func object. */
+	lua_getfield(L, LUA_GLOBALSINDEX, "box");
+	lua_pushstring(L, "schema");
+	lua_gettable(L, -2);
+	lua_pushstring(L, "func");
+	lua_gettable(L, -2);
+	lua_pushstring(L, "bless");
+	lua_gettable(L, -2);
+
+	lua_pushvalue(L, top);
+	lua_call(L, 1, 0);
+	lua_pop(L, 3);
+
+	lua_setfield(L, -2, func->def->name);
+
+	lua_pop(L, 2);
+}
+
+static void
+lbox_func_delete(struct lua_State *L, uint32_t fid)
+{
+	lua_getfield(L, LUA_GLOBALSINDEX, "box");
+	lua_getfield(L, -1, "func");
+	lua_rawgeti(L, -1, fid);
+	if (!lua_isnil(L, -1)) {
+		lua_getfield(L, -1, "name");
+		lua_pushnil(L);
+		lua_rawset(L, -4);
+		lua_pop(L, 1); /* pop func */
+
+		lua_pushnil(L);
+		lua_rawseti(L, -2, fid);
+	} else {
+		lua_pop(L, 1);
+	}
+	lua_pop(L, 2); /* box, func */
+}
+
+static void
+lbox_func_new_or_delete(struct trigger *trigger, void *event)
+{
+	struct lua_State *L = (struct lua_State *) trigger->data;
+	uint32_t fid = (uint32_t)(uintptr_t)event;
+	struct func *func = func_by_id(fid);
+	/* Export only persistent Lua functions. */
+	if (func != NULL)
+		lbox_func_new(L, func);
+	else
+		lbox_func_delete(L, fid);
+}
+
+static struct trigger on_alter_func_in_lua = {
+	RLIST_LINK_INITIALIZER, lbox_func_new_or_delete, NULL, NULL
+};
+
+void
+box_lua_func_init(struct lua_State *L)
+{
+	/*
+	 * Register the trigger that will push persistent
+	 * Lua functions objects to Lua.
+	 */
+	on_alter_func_in_lua.data = L;
+	trigger_add(&on_alter_func, &on_alter_func_in_lua);
+	static const struct luaL_Reg funclib [] = {
+		{NULL, NULL}
+	};
+	luaL_register_module(L, "box.func", funclib);
+	lua_pop(L, 1);
+}
diff --git a/src/box/func.h b/src/box/func.h
index 7b920d7d3..89f503eea 100644
--- a/src/box/func.h
+++ b/src/box/func.h
@@ -44,6 +44,7 @@ extern "C" {
 
 struct port;
 struct func_vtab;
+struct lua_State;
 
 /**
  * Dynamic shared module.
@@ -160,6 +161,10 @@ int
 box_func_execute_by_name(const char *name, uint32_t name_len, struct port *port,
 			 const char *args, const char *args_end);
 
+/** Initialize Lua export trigger for _func objects. */
+void
+box_lua_func_init(struct lua_State *L);
+
 #if defined(__cplusplus)
 } /* extern "C" */
 #endif /* defined(__cplusplus) */
diff --git a/src/box/lua/call.c b/src/box/lua/call.c
index e40c9a3f7..f85e1fc30 100644
--- a/src/box/lua/call.c
+++ b/src/box/lua/call.c
@@ -29,6 +29,7 @@
  * SUCH DAMAGE.
  */
 #include "box/lua/call.h"
+#include "box/lua/misc.h"
 #include "box/call.h"
 #include "box/error.h"
 #include "box/func.h"
@@ -126,10 +127,31 @@ lbox_module_reload(lua_State *L)
 	return 0;
 }
 
+int
+lbox_func_call(struct lua_State *L)
+{
+	if (lua_gettop(L) != 2 || !lua_isstring(L, 1))
+		return luaL_error(L, "Usage func:call(table)");
+
+	size_t name_len;
+	const char *name = lua_tolstring(L, 1, &name_len);
+	size_t tuple_len;
+	const char *tuple = lbox_encode_tuple_on_gc(L, 2, &tuple_len);
+
+	struct port port;
+	if (box_func_execute_by_name(name, name_len, &port, tuple,
+				     tuple + tuple_len) != 0)
+		return luaT_error(L);
+	port_dump_lua(&port, L);
+	port_destroy(&port);
+	return 1;
+}
+
 static const struct luaL_Reg boxlib_internal[] = {
 	{"call_loadproc",  lbox_call_loadproc},
 	{"sql_create_function",  lbox_sql_create_function},
 	{"module_reload", lbox_module_reload},
+	{"func_call", lbox_func_call},
 	{NULL, NULL}
 };
 
diff --git a/src/box/lua/init.c b/src/box/lua/init.c
index 76b987b4b..67a822eb4 100644
--- a/src/box/lua/init.c
+++ b/src/box/lua/init.c
@@ -39,6 +39,7 @@
 
 #include "box/box.h"
 #include "box/txn.h"
+#include "box/func.h"
 #include "box/vclock.h"
 
 #include "box/lua/error.h"
@@ -315,6 +316,7 @@ box_lua_init(struct lua_State *L)
 	box_lua_xlog_init(L);
 	box_lua_execute_init(L);
 	luaopen_net_box(L);
+	box_lua_func_init(L);
 	lua_pop(L, 1);
 	tarantool_lua_console_init(L);
 	lua_pop(L, 1);
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index b5393e19a..1b09758d9 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -2164,7 +2164,32 @@ function box.schema.func.exists(name_or_id)
     return tuple ~= nil
 end
 
+-- Helper function to check func:method() usage
+local function check_func_arg(func, method)
+    if type(func) ~= 'table' or func.name == nil then
+        local fmt = 'Use func:%s(...) instead of func.%s(...)'
+        error(string.format(fmt, method, method))
+    end
+end
+
+local func_f_mt = {}
+
+func_f_mt.drop = function(func, opts)
+    check_func_arg(func, 'drop')
+    box.schema.func.drop(func.name, opts)
+end
+
+func_f_mt.call = function(func, args)
+    check_func_arg(func, 'call')
+    return box.schema.func.call(func.name, args or {})
+end
+
+function box.schema.func.bless(func)
+    setmetatable(func, {__index = func_f_mt})
+end
+
 box.schema.func.reload = internal.module_reload
+box.schema.func.call = internal.func_call
 
 box.internal.collation = {}
 box.internal.collation.create = function(name, coll_type, locale, opts)
diff --git a/src/box/schema.cc b/src/box/schema.cc
index 0508482a6..90b18b347 100644
--- a/src/box/schema.cc
+++ b/src/box/schema.cc
@@ -72,6 +72,7 @@ uint32_t space_cache_version = 0;
 struct rlist on_schema_init = RLIST_HEAD_INITIALIZER(on_schema_init);
 struct rlist on_alter_space = RLIST_HEAD_INITIALIZER(on_alter_space);
 struct rlist on_alter_sequence = RLIST_HEAD_INITIALIZER(on_alter_sequence);
+struct rlist on_alter_func = RLIST_HEAD_INITIALIZER(on_alter_func);
 
 /**
  * Lock of scheme modification
diff --git a/src/box/schema.h b/src/box/schema.h
index 368463536..e569e3f9d 100644
--- a/src/box/schema.h
+++ b/src/box/schema.h
@@ -130,6 +130,11 @@ int
 schema_find_id(uint32_t system_space_id, uint32_t index_id, const char *name,
 	       uint32_t len, uint32_t *object_id);
 
+struct func;
+
+struct func *
+func_by_id(uint32_t fid);
+
 #if defined(__cplusplus)
 } /* extern "C" */
 
@@ -178,11 +183,6 @@ func_cache_replace(struct func *new_func, struct func **old_func);
 void
 func_cache_delete(uint32_t fid);
 
-struct func;
-
-struct func *
-func_by_id(uint32_t fid);
-
 static inline struct func *
 func_cache_find(uint32_t fid)
 {
@@ -243,6 +243,12 @@ extern struct rlist on_alter_sequence;
  */
 extern struct rlist on_access_denied;
 
+/**
+ * Triggers fired after committing a change in _func space.
+ * It is passed the txn statement that altered the space.
+ */
+extern struct rlist on_alter_func;
+
 /**
  * Context passed to on_access_denied trigger.
  */
diff --git a/test/box/misc.result b/test/box/misc.result
index a4c8ae498..c1a81f75b 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -65,6 +65,7 @@ t
   - error
   - execute
   - feedback
+  - func
   - index
   - info
   - internal
diff --git a/test/box/persistent_func.result b/test/box/persistent_func.result
index 54548f3b0..bf0a123e2 100644
--- a/test/box/persistent_func.result
+++ b/test/box/persistent_func.result
@@ -86,6 +86,56 @@ math.abs(1)
 box.schema.func.drop('monkey')
 ---
 ...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+body = [[function(array)
+		local min = 999
+		local max = -1
+		for _, v in pairs(array) do
+			min = math.min(min, v)
+			max = math.max(max, v)
+		end
+		return min, max
+	end]]
+test_run:cmd("setopt delimiter ''");
+---
+...
+box.schema.func.create("minmax", {body = body, opts = {is_sandboxed = true}})
+---
+...
+box.func.minmax
+---
+- is_sandboxed: true
+  returns: any
+  is_deterministic: false
+  id: 4
+  language: LUA
+  name: minmax
+  is_persistent: true
+  setuid: false
+...
+array = {1, 3, 2, 5, 9}
+---
+...
+box.func.minmax:call({array})
+---
+- - 9
+  - 1
+...
+conn:call("minmax", {array})
+---
+- 1
+- 9
+...
+box.func.minmax:drop()
+---
+...
+box.schema.func.exists("minmax")
+---
+- false
+...
 -- Test function with spell error - case 1.
 test_run:cmd("setopt delimiter ';'")
 ---
@@ -170,3 +220,38 @@ box.schema.func.drop('test2')
 box.schema.user.revoke('guest', 'execute', 'universe')
 ---
 ...
+box.schema.func.create("secret", {body = "function() return 1 end"})
+---
+...
+box.func.secret:call({})
+---
+- - 1
+...
+function secret_leak() return box.func.secret:call() end
+---
+...
+box.schema.func.create('secret_leak')
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'secret_leak')
+---
+...
+conn = net.connect(box.cfg.listen)
+---
+...
+conn:call('secret_leak')
+---
+- error: Execute access to function 'secret' is denied for user 'guest'
+...
+conn:close()
+---
+...
+box.schema.user.revoke('guest', 'execute', 'function', 'secret_leak')
+---
+...
+box.schema.func.drop('secret_leak')
+---
+...
+box.schema.func.drop('secret')
+---
+...
diff --git a/test/box/persistent_func.test.lua b/test/box/persistent_func.test.lua
index 095a27872..062c3b060 100644
--- a/test/box/persistent_func.test.lua
+++ b/test/box/persistent_func.test.lua
@@ -39,6 +39,26 @@ conn:call("monkey", {1})
 math.abs(1)
 box.schema.func.drop('monkey')
 
+test_run:cmd("setopt delimiter ';'")
+body = [[function(array)
+		local min = 999
+		local max = -1
+		for _, v in pairs(array) do
+			min = math.min(min, v)
+			max = math.max(max, v)
+		end
+		return min, max
+	end]]
+test_run:cmd("setopt delimiter ''");
+box.schema.func.create("minmax", {body = body, opts = {is_sandboxed = true}})
+box.func.minmax
+array = {1, 3, 2, 5, 9}
+box.func.minmax:call({array})
+conn:call("minmax", {array})
+
+box.func.minmax:drop()
+box.schema.func.exists("minmax")
+
 -- Test function with spell error - case 1.
 test_run:cmd("setopt delimiter ';'")
 body_bad2 = [[function(tuple)
@@ -74,3 +94,19 @@ box.schema.func.drop('test')
 box.schema.func.exists('test')
 box.schema.func.drop('test2')
 box.schema.user.revoke('guest', 'execute', 'universe')
+
+
+box.schema.func.create("secret", {body = "function() return 1 end"})
+box.func.secret:call({})
+
+function secret_leak() return box.func.secret:call() end
+box.schema.func.create('secret_leak')
+box.schema.user.grant('guest', 'execute', 'function', 'secret_leak')
+
+conn = net.connect(box.cfg.listen)
+conn:call('secret_leak')
+
+conn:close()
+box.schema.user.revoke('guest', 'execute', 'function', 'secret_leak')
+box.schema.func.drop('secret_leak')
+box.schema.func.drop('secret')
-- 
2.21.0




More information about the Tarantool-patches mailing list