From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: From: Kirill Shcherbatov Subject: [PATCH v2 4/9] box: rework func object as a function frontend Date: Thu, 6 Jun 2019 15:04:00 +0300 Message-Id: <8902e130a9cd1ac295391fc9cd315ad4cfa2bafa.1559822429.git.kshcherbatov@tarantool.org> In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit To: tarantool-patches@freelists.org, vdavydov.dev@gmail.com Cc: Kirill Shcherbatov List-ID: The function func object used to provide a call method only for C functions. In scope of this patch it reworked to be a uniform function call frontend both for C and Lua functions. Needed for #4182, #1260 --- src/box/call.c | 136 +----------------- src/box/call.h | 4 - src/box/func.c | 337 ++++++++++++++++++++++++++++++++++++++++----- src/box/func.h | 29 +++- src/box/lua/call.c | 39 ------ 5 files changed, 332 insertions(+), 213 deletions(-) diff --git a/src/box/call.c b/src/box/call.c index 982c95cbf..3e64118c9 100644 --- a/src/box/call.c +++ b/src/box/call.c @@ -43,149 +43,17 @@ #include "small/obuf.h" #include "tt_static.h" -/** - * Find a function by name and check "EXECUTE" permissions. - * - * @param name function name - * @param name_len length of @a name - * @param[out] funcp function object - * Sic: *pfunc == NULL means that perhaps the user has a global - * "EXECUTE" privilege, so no specific grant to a function. - * - * @retval -1 on access denied - * @retval 0 on success - */ -static inline int -access_check_func(const char *name, uint32_t name_len, struct func **funcp) -{ - struct func *func = func_by_name(name, name_len); - struct credentials *credentials = effective_user(); - /* - * If the user has universal access, don't bother with checks. - * No special check for ADMIN user is necessary - * since ADMIN has universal access. - */ - if ((credentials->universal_access & (PRIV_X | PRIV_U)) == - (PRIV_X | PRIV_U)) { - - *funcp = func; - return 0; - } - user_access_t access = PRIV_X | PRIV_U; - /* Check access for all functions. */ - access &= ~entity_access_get(SC_FUNCTION)[credentials->auth_token].effective; - user_access_t func_access = access & ~credentials->universal_access; - if (func == NULL || - /* Check for missing Usage access, ignore owner rights. */ - func_access & PRIV_U || - /* Check for missing specific access, respect owner rights. */ - (func->def->uid != credentials->uid && - func_access & ~func->access[credentials->auth_token].effective)) { - - /* Access violation, report error. */ - struct user *user = user_find(credentials->uid); - if (user != NULL) { - if (!(access & credentials->universal_access)) { - diag_set(AccessDeniedError, - priv_name(PRIV_U), - schema_object_name(SC_UNIVERSE), "", - user->def->name); - } else { - diag_set(AccessDeniedError, - priv_name(PRIV_X), - schema_object_name(SC_FUNCTION), - tt_cstr(name, name_len), - user->def->name); - } - } - return -1; - } - - *funcp = func; - return 0; -} - -static int -box_c_call(struct func *func, struct call_request *request, struct port *port) -{ - assert(func != NULL && func->def->language == FUNC_LANGUAGE_C); - - /* Create a call context */ - port_tuple_create(port); - box_function_ctx_t ctx = { port }; - - /* Clear all previous errors */ - diag_clear(&fiber()->diag); - assert(!in_txn()); /* transaction is not started */ - - /* Call function from the shared library */ - int rc = func_call(func, &ctx, request->args, request->args_end); - func = NULL; /* May be deleted by DDL */ - if (rc != 0) { - if (diag_last_error(&fiber()->diag) == NULL) { - /* Stored procedure forget to set diag */ - diag_set(ClientError, ER_PROC_C, "unknown error"); - } - port_destroy(port); - return -1; - } - return 0; -} - int box_process_call(struct call_request *request, struct port *port) { rmean_collect(rmean_box, IPROTO_CALL, 1); - /** - * Find the function definition and check access. - */ const char *name = request->name; assert(name != NULL); uint32_t name_len = mp_decode_strl(&name); - struct func *func = NULL; - /** - * Sic: func == NULL means that perhaps the user has a global - * "EXECUTE" privilege, so no specific grant to a function. - */ - if (access_check_func(name, name_len, &func) != 0) - return -1; /* permission denied */ - - /** - * Change the current user id if the function is - * a set-definer-uid one. If the function is not - * defined, it's obviously not a setuid one. - */ - struct credentials *orig_credentials = NULL; - if (func && func->def->setuid) { - orig_credentials = effective_user(); - /* Remember and change the current user id. */ - if (func->owner_credentials.auth_token >= BOX_USER_MAX) { - /* - * Fill the cache upon first access, since - * when func is created, no user may - * be around to fill it (recovery of - * system spaces from a snapshot). - */ - struct user *owner = user_find(func->def->uid); - if (owner == NULL) - return -1; - credentials_init(&func->owner_credentials, - owner->auth_token, - owner->def->uid); - } - fiber_set_user(fiber(), &func->owner_credentials); - } - int rc; - if (func && func->def->language == FUNC_LANGUAGE_C) { - rc = box_c_call(func, request, port); - } else { - rc = box_lua_call(request, port); - } - /* Restore the original user */ - if (orig_credentials) - fiber_set_user(fiber(), orig_credentials); + rc = box_func_execute_by_name(name, name_len, port, request->args, + request->args_end); if (rc != 0) { txn_rollback(); diff --git a/src/box/call.h b/src/box/call.h index 4b3b8614c..05032c0d8 100644 --- a/src/box/call.h +++ b/src/box/call.h @@ -38,10 +38,6 @@ extern "C" { struct port; struct call_request; -struct box_function_ctx { - struct port *port; -}; - int box_process_call(struct call_request *request, struct port *port); diff --git a/src/box/func.c b/src/box/func.c index 5051286a3..71c6bb6eb 100644 --- a/src/box/func.c +++ b/src/box/func.c @@ -32,7 +32,12 @@ #include "trivia/config.h" #include "assoc.h" #include "session.h" +#include "lua/msgpack.h" #include "lua/utils.h" +#include "schema.h" +#include "txn.h" +#include "tt_static.h" +#include "port.h" #include "error.h" #include "diag.h" #include @@ -326,8 +331,8 @@ module_reload(const char *package, const char *package_end, struct module **modu rlist_foreach_entry_safe(func, &old_module->funcs, item, tmp_func) { struct func_name name; func_split_name(func->def->name, &name); - func->func = module_sym(new_module, name.sym); - if (func->func == NULL) + func->c_func = module_sym(new_module, name.sym); + if (func->c_func == NULL) goto restore; func->module = new_module; rlist_move(&new_module->funcs, &func->item); @@ -347,8 +352,8 @@ restore: do { struct func_name name; func_split_name(func->def->name, &name); - func->func = module_sym(old_module, name.sym); - if (func->func == NULL) { + func->c_func = module_sym(old_module, name.sym); + if (func->c_func == NULL) { /* * Something strange was happen, an early loaden * function was not found in an old dso. @@ -378,6 +383,25 @@ box_module_reload(const char *name) return -1; } +/** Private virtual method table for func object. */ +struct func_vtab { + /** Load function runtime environment. */ + int (*load)(struct func *func); + /** Unload function runtime environment. */ + void (*unload)(struct func *func); + /** Call function with given arguments. */ + int (*call)(struct func *func, struct port *port, const char *args, + const char *args_end); + /** + * Capture a given old_func's runtime and reuse in a given + * new function if possible. + */ + void (*reuse_runtime)(struct func *new_func, struct func *old_func); +}; + +static struct func_vtab func_c_vtab; +static struct func_vtab func_lua_vtab; + struct func * func_new(struct func_def *def) { @@ -401,15 +425,44 @@ func_new(struct func_def *def) * checks (see user_has_data()). */ func->owner_credentials.auth_token = BOX_USER_MAX; /* invalid value */ - func->func = NULL; - func->module = NULL; + if (def->language == FUNC_LANGUAGE_C) { + func->c_func = NULL; + func->module = NULL; + func->vtab = &func_c_vtab; + /* + * Due to the fact that the function load cause + * loading of a shared library, this action is + * performed on demand: so we guarantee that it is + * performed by the user who has the authority + * to do it. + */ + } else { + assert(def->language == FUNC_LANGUAGE_LUA); + func->vtab = &func_lua_vtab; + } return func; } +void +func_delete(struct func *func) +{ + func->vtab->unload(func); + free(func->def); + free(func); +} + +void +func_reuse_runtime(struct func *new_func, struct func *old_func) +{ + new_func->vtab->reuse_runtime(new_func, old_func); +} + static void -func_unload(struct func *func) +func_c_unload(struct func *func) { - if (func->module) { + assert(func->vtab == &func_c_vtab); + assert(func != NULL && func->def->language == FUNC_LANGUAGE_C); + if (func->module != NULL) { rlist_del(&func->item); if (rlist_empty(&func->module->funcs)) { struct func_name name; @@ -418,8 +471,8 @@ func_unload(struct func *func) } module_gc(func->module); } + func->c_func = NULL; func->module = NULL; - func->func = NULL; } /** @@ -427,9 +480,10 @@ func_unload(struct func *func) * symbol from it). */ static int -func_load(struct func *func) +func_c_load(struct func *func) { - assert(func->func == NULL); + assert(func != NULL && func->def->language == FUNC_LANGUAGE_C); + assert(func->vtab == &func_c_vtab); struct func_name name; func_split_name(func->def->name, &name); @@ -447,46 +501,265 @@ func_load(struct func *func) } } - func->func = module_sym(module, name.sym); - if (func->func == NULL) + func->c_func = module_sym(module, name.sym); + if (func->c_func == NULL) return -1; func->module = module; rlist_add(&module->funcs, &func->item); return 0; } -int -func_call(struct func *func, box_function_ctx_t *ctx, const char *args, - const char *args_end) +static int +func_c_call(struct func *func, struct port *port, const char *args, + const char *args_end) { - if (func->func == NULL) { - if (func_load(func) != 0) - return -1; - } + assert(func != NULL && func->def->language == FUNC_LANGUAGE_C); + assert(func->vtab == &func_c_vtab); + assert(!in_txn()); + + port_tuple_create(port); + if (func->c_func == NULL && func_c_load(func) != 0) + return -1; /* Module can be changed after function reload. */ struct module *module = func->module; assert(module != NULL); ++module->calls; - int rc = func->func(ctx, args, args_end); + box_function_ctx_t ctx = { port }; + int rc = func->c_func(&ctx, args, args_end); --module->calls; module_gc(module); + if (rc != 0 && diag_last_error(&fiber()->diag) == NULL) { + /* Stored procedure forget to set diag */ + diag_set(ClientError, ER_PROC_C, "unknown error"); + } return rc; } -void -func_delete(struct func *func) +static void +func_c_reuse_runtime(struct func *new_func, struct func *old_func) { - func_unload(func); - free(func->def); - free(func); -} + assert(new_func != NULL && new_func->def->language == FUNC_LANGUAGE_C); + assert(new_func->vtab == &func_c_vtab); + assert(old_func != NULL && old_func->def->language == FUNC_LANGUAGE_C); + assert(old_func->vtab == &func_c_vtab); -void -func_reuse_runtime(struct func *new_func, struct func *old_func) -{ new_func->module = old_func->module; - new_func->func = old_func->func; + new_func->c_func = old_func->c_func; old_func->module = NULL; - old_func->func = NULL; + old_func->c_func = NULL; +} + +static struct func_vtab func_c_vtab = { + .load = func_c_load, + .unload = func_c_unload, + .call = func_c_call, + .reuse_runtime = func_c_reuse_runtime, +}; + +static void +func_lua_unload(struct func *func) +{ + assert(func != NULL && func->def->language == FUNC_LANGUAGE_LUA); + assert(func->vtab == &func_lua_vtab); + (void) func; +} + +struct func_lua_call_impl_ctx { + const char *args; + const char *args_end; + const char *func_name; + const char *func_name_end; +}; + +static int +func_lua_call_impl_cb(struct lua_State *L) +{ + struct func_lua_call_impl_ctx *ctx = + (struct func_lua_call_impl_ctx *) lua_topointer(L, 1); + lua_settop(L, 0); + + int oc = 0; + if (luaT_func_find(L, ctx->func_name, ctx->func_name_end, &oc) != 0) { + diag_set(ClientError, ER_NO_SUCH_PROC, + ctx->func_name_end - ctx->func_name, ctx->func_name); + return luaT_error(L); + } + + const char *args = ctx->args; + uint32_t arg_count = mp_decode_array(&args); + luaL_checkstack(L, arg_count, "call: out of stack"); + for (uint32_t i = 0; i < arg_count; i++) + luamp_decode(L, luaL_msgpack_default, &args); + lua_call(L, arg_count + oc - 1, LUA_MULTRET); + return lua_gettop(L); +} + +static inline int +func_lua_call_impl(lua_CFunction handler, struct func_lua_call_impl_ctx *ctx, + struct port *port) +{ + struct lua_State *L = lua_newthread(tarantool_L); + int coro_ref = luaL_ref(tarantool_L, LUA_REGISTRYINDEX); + port_lua_create(port, L); + ((struct port_lua *) port)->ref = coro_ref; + + lua_pushcfunction(L, handler); + lua_pushlightuserdata(L, ctx); + if (luaT_call(L, 1, LUA_MULTRET) != 0) + return -1; + return 0; +} + +static int +func_lua_load(struct func *func) +{ + assert(func != NULL && func->def->language == FUNC_LANGUAGE_LUA); + assert(func->vtab == &func_lua_vtab); + (void) func; + return 0; +} + +static inline int +func_lua_call(struct func *func, struct port *port, const char *args, + const char *args_end) +{ + assert(func != NULL && func->def->language == FUNC_LANGUAGE_LUA); + assert(func->vtab == &func_lua_vtab); + + struct func_lua_call_impl_ctx ctx; + ctx.func_name = func->def->name; + ctx.func_name_end = func->def->name + strlen(func->def->name); + ctx.args = args; + ctx.args_end = args_end; + return func_lua_call_impl(func_lua_call_impl_cb, &ctx, port); +} + +static void +func_lua_reuse_runtime(struct func *new_func, struct func *old_func) +{ + assert(new_func != NULL && new_func->def->language == FUNC_LANGUAGE_LUA); + assert(new_func->vtab == &func_c_vtab); + assert(old_func != NULL && old_func->def->language == FUNC_LANGUAGE_LUA); + assert(old_func->vtab == &func_c_vtab); + (void)new_func; + (void)old_func; +} + +static struct func_vtab func_lua_vtab = { + .load = func_lua_load, + .unload = func_lua_unload, + .call = func_lua_call, + .reuse_runtime = func_lua_reuse_runtime, +}; + +/** Check "EXECUTE" permissions for a given function. */ +static int +func_access_check(struct func *func, const char *func_name) +{ + struct credentials *credentials = effective_user(); + /* + * If the user has universal access, don't bother with checks. + * No special check for ADMIN user is necessary + * since ADMIN has universal access. + */ + if ((credentials->universal_access & (PRIV_X | PRIV_U)) == + (PRIV_X | PRIV_U)) + return 0; + user_access_t access = PRIV_X | PRIV_U; + /* Check access for all functions. */ + access &= ~entity_access_get(SC_FUNCTION)[credentials->auth_token].effective; + user_access_t func_access = access & ~credentials->universal_access; + if (func == NULL || + /* Check for missing Usage access, ignore owner rights. */ + func_access & PRIV_U || + /* Check for missing specific access, respect owner rights. */ + (func->def->uid != credentials->uid && + func_access & ~func->access[credentials->auth_token].effective)) { + /* Access violation, report error. */ + struct user *user = user_find(credentials->uid); + if (user != NULL) { + if (!(access & credentials->universal_access)) { + diag_set(AccessDeniedError, priv_name(PRIV_U), + schema_object_name(SC_UNIVERSE), "", + user->def->name); + } else { + diag_set(AccessDeniedError, priv_name(PRIV_X), + schema_object_name(SC_FUNCTION), + func_name, user->def->name); + } + } + return -1; + } + return 0; +} + +int +func_execute(struct func *func, struct port *port, const char *args, + const char *args_end) +{ + assert(func_access_check(func, func->def->name) == 0); + /** + * Change the current user id if the function is + * a set-definer-uid one. If the function is not + * defined, it's obviously not a setuid one. + */ + struct credentials *orig_credentials = NULL; + if (func->def->setuid) { + orig_credentials = effective_user(); + /* Remember and change the current user id. */ + if (func->owner_credentials.auth_token >= BOX_USER_MAX) { + /* + * Fill the cache upon first access, since + * when func is created, no user may + * be around to fill it (recovery of + * system spaces from a snapshot). + */ + struct user *owner = user_find(func->def->uid); + if (owner == NULL) + return -1; + credentials_init(&func->owner_credentials, + owner->auth_token, + owner->def->uid); + } + fiber_set_user(fiber(), &func->owner_credentials); + } + int rc = func->vtab->call(func, port, args, args_end); + if (rc != 0) + port_destroy(port); + /* Restore the original user */ + if (orig_credentials) + fiber_set_user(fiber(), orig_credentials); + return rc; +} + +int +box_func_execute_by_name(const char *name, uint32_t name_len, struct port *port, + const char *args, const char *args_end) +{ + struct func *func = func_by_name(name, name_len); + if (func_access_check(func, tt_cstr(name, name_len)) != 0) + return -1; + /* Clear all previous errors */ + diag_clear(&fiber()->diag); + int rc = 0; + if (func != NULL) { + rc = func_execute(func, port, args, args_end); + } else { + /* + * A user that has "universe" access may call + * any Lua function, even not registered when + * it exists. + */ + struct func_lua_call_impl_ctx ctx; + ctx.func_name = name; + ctx.func_name_end = name + name_len; + ctx.args = args; + ctx.args_end = args_end; + rc = func_lua_call_impl(func_lua_call_impl_cb, &ctx, port); + if (rc != 0) + port_destroy(port); + } + assert(rc == 0 || diag_last_error(&fiber()->diag) != NULL); + return rc; } diff --git a/src/box/func.h b/src/box/func.h index 82ac046b7..931718bba 100644 --- a/src/box/func.h +++ b/src/box/func.h @@ -42,6 +42,9 @@ extern "C" { #endif /* defined(__cplusplus) */ +struct port; +struct func_vtab; + /** * Dynamic shared module. */ @@ -61,6 +64,8 @@ struct module { */ struct func { struct func_def *def; + /** Virtual method table. */ + struct func_vtab *vtab; /** * Anchor for module membership. */ @@ -68,7 +73,7 @@ struct func { /** * For C functions, the body of the function. */ - box_function_f func; + box_function_f c_func; /** * Each stored function keeps a handle to the * dynamic library for the C callback. @@ -97,6 +102,10 @@ module_init(void); void module_free(void); +struct box_function_ctx { + struct port *port; +}; + struct func * func_new(struct func_def *def); @@ -104,11 +113,12 @@ void func_delete(struct func *func); /** - * Call stored C function using @a args. + * Execute given function. + * The current user must have access to execute it. */ int -func_call(struct func *func, box_function_ctx_t *ctx, const char *args, - const char *args_end); +func_execute(struct func *func, struct port *port, const char *args, + const char *args_end); /** * Capture a given old_func's runtime an reuse in a given new @@ -127,6 +137,17 @@ func_reuse_runtime(struct func *new_func, struct func *old_func); int box_module_reload(const char *name); +/** + * Check the current user privileges and execute a Tarantool + * function by the given name. + * When function object is not registered in function cache and + * the user has "universe" privileges, lookup for a given name + * in a Lua global namespace and execute when exists. + */ +int +box_func_execute_by_name(const char *name, uint32_t name_len, struct port *port, + const char *args, const char *args_end); + #if defined(__cplusplus) } /* extern "C" */ #endif /* defined(__cplusplus) */ diff --git a/src/box/lua/call.c b/src/box/lua/call.c index 9edb5bf7e..4d4521363 100644 --- a/src/box/lua/call.c +++ b/src/box/lua/call.c @@ -221,39 +221,6 @@ port_lua_create(struct port *port, struct lua_State *L) port_lua->ref = -1; } -static int -execute_lua_call(lua_State *L) -{ - struct call_request *request = (struct call_request *) - lua_topointer(L, 1); - lua_settop(L, 0); /* clear the stack to simplify the logic below */ - - const char *name = request->name; - uint32_t name_len = mp_decode_strl(&name); - - /* - * How many objects are on stack after - * luaT_func_find call. - */ - int oc = 0; - /* Try to find a function by name in Lua */ - if (luaT_func_find(L, name, name + name_len, &oc) != 0) { - diag_set(ClientError, ER_NO_SUCH_PROC, name_len, name); - return luaT_error(L); - } - - /* Push the rest of args (a tuple). */ - const char *args = request->args; - - uint32_t arg_count = mp_decode_array(&args); - luaL_checkstack(L, arg_count, "call: out of stack"); - - for (uint32_t i = 0; i < arg_count; i++) - luamp_decode(L, luaL_msgpack_default, &args); - lua_call(L, arg_count + oc - 1, LUA_MULTRET); - return lua_gettop(L); -} - static int execute_lua_eval(lua_State *L) { @@ -396,12 +363,6 @@ box_process_lua(struct call_request *request, struct port *base, return 0; } -int -box_lua_call(struct call_request *request, struct port *port) -{ - return box_process_lua(request, port, execute_lua_call); -} - int box_lua_eval(struct call_request *request, struct port *port) { -- 2.21.0