diff --git a/.gitignore b/.gitignore index 965a7d9fb40013600f53ec4bbba3f53bfa5b8d1d..1c7d39299fdb1c51e2502e9a420be7b47de2519b 100644 --- a/.gitignore +++ b/.gitignore @@ -48,8 +48,7 @@ src/box/lua/*.lua.c src/tarantool src/trivia/tarantool.h tarantool-*.tar.gz -test/lib/*.pyc -test/lib/*/*.pyc +test/lib/ test/unit/*.test test/unit/fiob test/var diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a4d3a4fbf9608b246f18275a98608e13ece6fe0..abb1fc0992dcdd3383d2d19b2eb47d829d7cec4f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,7 @@ include(cmake/profile.cmake) include(cmake/FindReadline.cmake) include(cmake/FindSphinx.cmake) include(cmake/systemd.cmake) +include(cmake/module.cmake) if (NOT READLINE_FOUND) message(FATAL_ERROR "readline library not found.") diff --git a/cmake/module.cmake b/cmake/module.cmake new file mode 100644 index 0000000000000000000000000000000000000000..c8015a0417483c0bd65484edc643b8abfecec50e --- /dev/null +++ b/cmake/module.cmake @@ -0,0 +1,29 @@ +# A helper function to extract public API +function(rebuild_module_api) + set (dstfile "${CMAKE_CURRENT_BINARY_DIR}/tarantool.h") + set (tmpfile "${dstfile}.new") + set (headers) + # Get absolute path for header files (required of out-of-source build) + foreach (header ${ARGN}) + if (IS_ABSOLUTE ${header}) + list(APPEND headers ${header}) + else() + list(APPEND headers ${CMAKE_CURRENT_SOURCE_DIR}/${header}) + endif() + endforeach() + + add_custom_command(OUTPUT ${dstfile} + COMMAND cat ${CMAKE_CURRENT_SOURCE_DIR}/tarantool_header.h > ${tmpfile} + COMMAND cat ${headers} | ${CMAKE_SOURCE_DIR}/extra/apigen >> ${tmpfile} + COMMAND cat ${CMAKE_CURRENT_SOURCE_DIR}/tarantool_footer.h >> ${tmpfile} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${tmpfile} ${dstfile} + COMMAND ${CMAKE_COMMAND} -E remove ${tmpfile} + DEPENDS ${srcfiles} + ${CMAKE_CURRENT_SOURCE_DIR}/tarantool_header.h + ${CMAKE_CURRENT_SOURCE_DIR}/tarantool_footer.h + ) + + add_custom_target(rebuild_module_api ALL DEPENDS ${srcfiles} ${dstfile}) + install(FILES ${dstfile} DESTINATION ${MODULE_INCLUDEDIR}) +endfunction() +set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/tarantool.h" PROPERTIES GENERATED HEADER_FILE_ONLY) diff --git a/cmake/utils.cmake b/cmake/utils.cmake index c03c0878a19d151263fbb864b2bf69b03bf3da2a..d7cbc4a58d515ddded4971d937bf1c5f6fa81dda 100644 --- a/cmake/utils.cmake +++ b/cmake/utils.cmake @@ -80,31 +80,3 @@ function(bin_source varname srcfile dstfile) endfunction() -# A helper function to extract public API -function(apigen) - set (dstfile "${CMAKE_CURRENT_BINARY_DIR}/tarantool.h") - set (tmpfile "${dstfile}.new") - set (headers) - # Get absolute path for header files (required of out-of-source build) - foreach (header ${ARGN}) - if (IS_ABSOLUTE ${header}) - list(APPEND headers ${header}) - else() - list(APPEND headers ${CMAKE_CURRENT_SOURCE_DIR}/${header}) - endif() - endforeach() - - add_custom_command(OUTPUT ${dstfile} - COMMAND cat ${CMAKE_CURRENT_SOURCE_DIR}/tarantool_header.h > ${tmpfile} - COMMAND cat ${headers} | ${CMAKE_SOURCE_DIR}/extra/apigen >> ${tmpfile} - COMMAND cat ${CMAKE_CURRENT_SOURCE_DIR}/tarantool_footer.h >> ${tmpfile} - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${tmpfile} ${dstfile} - COMMAND ${CMAKE_COMMAND} -E remove ${tmpfile} - DEPENDS ${srcfiles} - ${CMAKE_CURRENT_SOURCE_DIR}/tarantool_header.h - ${CMAKE_CURRENT_SOURCE_DIR}/tarantool_footer.h - ) - - add_custom_target(generate_module_api ALL DEPENDS ${srcfiles} ${dstfile}) - install(FILES ${dstfile} DESTINATION ${MODULE_INCLUDEDIR}) -endfunction() diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt index 7ba096bdd009d0a2258f9d90c1555701ef437dfd..20464e854c4ec5097d1f66ddaa55fc113e3d45a8 100644 --- a/src/box/CMakeLists.txt +++ b/src/box/CMakeLists.txt @@ -40,10 +40,11 @@ add_library(box sophia_engine.cc sophia_index.cc space.cc + func.cc alter.cc schema.cc session.cc - port.cc + port.c request.cc txn.cc box.cc diff --git a/src/box/alter.cc b/src/box/alter.cc index 88b6e52e6e5c37aa0ff425dffc8b02b793e395ae..6fe1c0fa2393bb2e154a395cd381711656d3ac64 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -31,6 +31,7 @@ #include "user_def.h" #include "user.h" #include "space.h" +#include "func.h" #include "txn.h" #include "tuple.h" #include "fiber.h" /* for gc_pool */ @@ -71,6 +72,8 @@ struct latch schema_lock = LATCH_INITIALIZER(schema_lock); /** _func columns */ #define FUNC_SETUID 3 +/** _func columns */ +#define FUNC_LANGUAGE 4 /* {{{ Auxiliary functions and methods. */ @@ -1306,34 +1309,32 @@ on_replace_dd_user(struct trigger * /* trigger */, void *event) /** Create a function definition from tuple. */ static void -func_def_create_from_tuple(struct func_def *func, struct tuple *tuple) +func_def_create_from_tuple(struct func_def *def, struct tuple *tuple) { - func->fid = tuple_field_u32(tuple, ID); - func->uid = tuple_field_u32(tuple, UID); - func->setuid = false; - /* - * Do not initialize the privilege cache right away since - * when loading up a function definition during recovery, - * user cache may not be filled up yet (space _user is - * recovered after space _func), so no user cache entry - * may exist yet for such user. The cache will be filled - * up on demand upon first access. - * - * Later on consistency of the cache is ensured by DDL - * checks (see user_has_data()). - */ - func->owner_credentials.auth_token = BOX_USER_MAX; /* invalid value */ + def->fid = tuple_field_u32(tuple, ID); + def->uid = tuple_field_u32(tuple, UID); const char *name = tuple_field_cstr(tuple, NAME); uint32_t len = strlen(name); - if (len >= sizeof(func->name)) { + if (len >= sizeof(def->name)) { tnt_raise(ClientError, ER_CREATE_FUNCTION, name, "function name is too long"); } - snprintf(func->name, sizeof(func->name), "%s", name); - /** Nobody has access to the function but the owner. */ - memset(func->access, 0, sizeof(func->access)); + snprintf(def->name, sizeof(def->name), "%s", name); if (tuple_field_count(tuple) > FUNC_SETUID) - func->setuid = tuple_field_u32(tuple, FUNC_SETUID); + def->setuid = tuple_field_u32(tuple, FUNC_SETUID); + else + def->setuid = false; + if (tuple_field_count(tuple) > FUNC_LANGUAGE) { + const char *language = tuple_field_cstr(tuple, FUNC_LANGUAGE); + def->language = STR2ENUM(func_language, language); + if (def->language == func_language_MAX) { + tnt_raise(ClientError, ER_FUNCTION_LANGUAGE, + name, language); + } + } else { + /* Lua is the default. */ + def->language = FUNC_LANGUAGE_LUA; + } } /** Remove a function from function cache */ @@ -1352,9 +1353,9 @@ static void func_cache_replace_func(struct trigger * /* trigger */, void *event) { struct txn_stmt *stmt = txn_stmt((struct txn*) event); - struct func_def func; - func_def_create_from_tuple(&func, stmt->new_tuple); - func_cache_replace(&func); + struct func_def def; + func_def_create_from_tuple(&def, stmt->new_tuple); + func_cache_replace(&def); } /** @@ -1369,36 +1370,36 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event) struct txn_stmt *stmt = txn_stmt(txn); struct tuple *old_tuple = stmt->old_tuple; struct tuple *new_tuple = stmt->new_tuple; - struct func_def func; + struct func_def def; uint32_t fid = tuple_field_u32(old_tuple ? old_tuple : new_tuple, ID); - struct func_def *old_func = func_by_id(fid); + struct func *old_func = func_by_id(fid); if (new_tuple != NULL && old_func == NULL) { /* INSERT */ - func_def_create_from_tuple(&func, new_tuple); - func_cache_replace(&func); + func_def_create_from_tuple(&def, new_tuple); + func_cache_replace(&def); struct trigger *on_rollback = txn_alter_trigger_new(func_cache_remove_func, NULL); trigger_add(&txn->on_rollback, on_rollback); } else if (new_tuple == NULL) { /* DELETE */ - func_def_create_from_tuple(&func, old_tuple); + func_def_create_from_tuple(&def, old_tuple); /* * Can only delete func if you're the one * who created it or a superuser. */ - access_check_ddl(func.uid); + access_check_ddl(def.uid); /* Can only delete func if it has no grants. */ - if (schema_find_grants("function", old_func->fid)) { + if (schema_find_grants("function", old_func->def.fid)) { tnt_raise(ClientError, ER_DROP_FUNCTION, - (unsigned) func.uid, + (unsigned) old_func->def.uid, "function has grants"); } struct trigger *on_commit = txn_alter_trigger_new(func_cache_remove_func, NULL); trigger_add(&txn->on_commit, on_commit); } else { /* UPDATE, REPLACE */ - func_def_create_from_tuple(&func, new_tuple); - access_check_ddl(func.uid); + func_def_create_from_tuple(&def, new_tuple); + access_check_ddl(def.uid); struct trigger *on_commit = txn_alter_trigger_new(func_cache_replace_func, NULL); trigger_add(&txn->on_commit, on_commit); @@ -1462,8 +1463,8 @@ priv_def_check(struct priv_def *priv) } case SC_FUNCTION: { - struct func_def *func = func_cache_find(priv->object_id); - if (func->uid != grantor->uid && grantor->uid != ADMIN) { + struct func *func = func_cache_find(priv->object_id); + if (func->def.uid != grantor->uid && grantor->uid != ADMIN) { tnt_raise(ClientError, ER_ACCESS_DENIED, priv_name(priv->access), grantor->name); } diff --git a/src/box/errcode.h b/src/box/errcode.h index 68f770db0715b47ceec4e50ba4909223b34a2bd3..24226031a1103e2ee76ffb38c55af1f75adb15cc 100644 --- a/src/box/errcode.h +++ b/src/box/errcode.h @@ -150,6 +150,8 @@ struct errcode_record { /* 96 */_(ER_GUEST_USER_PASSWORD, 2, "Setting password for guest user has no effect") \ /* 97 */_(ER_TRANSACTION_CONFLICT, 2, "Transaction has been aborted by conflict") \ /* 98 */_(ER_UNSUPPORTED_ROLE_PRIV, 2, "Unsupported role privilege '%s'") \ + /* 98 */_(ER_LOAD_FUNCTION, 2, "Failed to dynamically load function '%s': %s") \ + /* 98 */_(ER_FUNCTION_LANGUAGE, 2, "Unsupported language '%s' specified for function '%s'") \ /* * !IMPORTANT! Please follow instructions at start of the file diff --git a/src/box/func.cc b/src/box/func.cc new file mode 100644 index 0000000000000000000000000000000000000000..21708cb6a0b2945c188ae2e63b0a4714ac43a6d7 --- /dev/null +++ b/src/box/func.cc @@ -0,0 +1,142 @@ +/* + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include "func.h" + +#include <dlfcn.h> + +#include "lua/utils.h" +#include "scoped_guard.h" + +struct func * +func_new(struct func_def *def) +{ + struct func *func = (struct func *) malloc(sizeof(struct func)); + if (func == NULL) + return NULL; + func->def = *def; + /** Nobody has access to the function but the owner. */ + memset(func->access, 0, sizeof(func->access)); + /* + * Do not initialize the privilege cache right away since + * when loading up a function definition during recovery, + * user cache may not be filled up yet (space _user is + * recovered after space _func), so no user cache entry + * may exist yet for such user. The cache will be filled + * up on demand upon first access. + * + * Later on consistency of the cache is ensured by DDL + * checks (see user_has_data()). + */ + func->owner_credentials.auth_token = BOX_USER_MAX; /* invalid value */ + func->func = NULL; + func->dlhandle = NULL; + return func; +} + +static void +func_unload(struct func *func) +{ + if (func->dlhandle) + dlclose(func->dlhandle); + func->dlhandle = NULL; + func->func = NULL; +} + +void +func_load(struct func *func) +{ + func_unload(func); + + struct lua_State *L = tarantool_L; + int n = lua_gettop(L); + + auto l_guard = make_scoped_guard([=]{ + lua_settop(L, n); + }); + /* + * Call package.searchpath(name, package.cpath) and use + * the path to the function in dlopen(). + */ + lua_getglobal(L, "package"); + lua_getfield(L, -1, "searchpath"); + + /* + * Extract package name from function name. + * E.g. name = foo.bar.baz, function = baz, package = foo.bar + */ + const char *package = func->def.name; + const char *package_end = NULL; + const char *sym = package; + while ((sym = strchr(sym, '.'))) + package_end = sym; + if (package_end == NULL) { + sym = package; + package_end = package + strlen(package); + } else { + sym = package_end + 1; + } + + /* First argument of searchpath: name */ + lua_pushlstring(L, package, package_end - package); + /* Fetch cpath from 'package' as the second argument */ + lua_getfield(L, -3, "cpath"); + + if (lua_pcall(L, 2, 1, 0)) { + tnt_raise(ClientError, ER_LOAD_FUNCTION, func->def.name, + lua_tostring(L, -1)); + } + if (lua_isnil(L, -1)) { + tnt_raise(ClientError, ER_LOAD_FUNCTION, func->def.name, + "shared library not found in the search path"); + } + func->dlhandle = dlopen(lua_tostring(L, -1), RTLD_NOW | RTLD_LOCAL); + if (func->dlhandle == NULL) { + tnt_raise(ClientError, ER_LOAD_FUNCTION, func->def.name, + dlerror()); + } + func->func = (box_function_f) dlsym(func->dlhandle, sym); + if (func->func == NULL) { + tnt_raise(ClientError, ER_LOAD_FUNCTION, func->def.name, + dlerror()); + } +} + +void +func_update(struct func *func, struct func_def *def) +{ + func_unload(func); + func->def = *def; +} + +void +func_delete(struct func *func) +{ + func_unload(func); + free(func); +} diff --git a/src/box/func.h b/src/box/func.h new file mode 100644 index 0000000000000000000000000000000000000000..cd402d5ec9ef64342d830a871e59b5383720be8d --- /dev/null +++ b/src/box/func.h @@ -0,0 +1,90 @@ +#ifndef TARANTOOL_BOX_FUNC_H_INCLUDED +#define TARANTOOL_BOX_FUNC_H_INCLUDED +/* + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include "key_def.h" + +extern "C" { + +/** \cond public */ +struct port; +struct request; + +/** + * API of C stored function. + */ +typedef int (*box_function_f)(struct request *request, + struct port *port); + +/** \endcond public */ +} /* extern "C" */ + + +/** + * Stored function. + */ +struct func { + struct func_def def; + /** + * For C functions, the body of the function. + */ + box_function_f func; + /** + * Each stored function keeps a handle to the + * dynamic library for the C callback. + */ + void *dlhandle; + /** + * Authentication id of the owner of the function, + * used for set-user-id functions. + */ + struct credentials owner_credentials; + /** + * Cached runtime access information. + */ + struct access access[BOX_USER_MAX]; +}; + +struct func * +func_new(struct func_def *def); + +void +func_update(struct func *func, struct func_def *def); + +void +func_delete(struct func *func); + +/** + * Resolve func->func (find the respective DLL and fetch the + * symbol from it). + */ +void +func_load(struct func *func); + +#endif /* TARANTOOL_BOX_FUNC_H_INCLUDED */ diff --git a/src/box/iproto_port.cc b/src/box/iproto_port.cc index 06110d6b8bcce5bbd6cadff0d27bf62f235fe2e4..a789457df5c0343622efc7d2e2488cd01d348e8b 100644 --- a/src/box/iproto_port.cc +++ b/src/box/iproto_port.cc @@ -108,7 +108,7 @@ iproto_port(struct port *port) enum { SVP_SIZE = sizeof(iproto_header_bin) + sizeof(iproto_body_bin) }; -static inline void +extern "C" void iproto_port_eof(struct port *ptr) { struct iproto_port *port = iproto_port(ptr); @@ -144,7 +144,7 @@ iproto_reply_select(struct obuf *buf, struct obuf_svp *svp, uint64_t sync, memcpy(pos + sizeof(header), &body, sizeof(body)); } -static inline void +extern "C" void iproto_port_add_tuple(struct port *ptr, struct tuple *tuple) { struct iproto_port *port = iproto_port(ptr); diff --git a/src/box/key_def.cc b/src/box/key_def.cc index 2f7fff6bee4aed9a70e5a1e1b0c49e411ece57cf..138479d2e47b501d67455d90323a17722c56bed2 100644 --- a/src/box/key_def.cc +++ b/src/box/key_def.cc @@ -35,6 +35,8 @@ const char *field_type_strs[] = {"UNKNOWN", "NUM", "STR", "ARRAY", "NUMBER", ""}; STRS(index_type, ENUM_INDEX_TYPE); +const char *func_language_strs[] = {"LUA", "C"}; + const uint32_t key_mp_type[] = { /* [UNKNOWN] = */ UINT32_MAX, /* [NUM] = */ 1U << MP_UINT, diff --git a/src/box/key_def.h b/src/box/key_def.h index 574b2651c000ae5c131cc2d83535a60a9b01c0f2..901ff6506ccd2b95e81882e6acffabc52ba8981c 100644 --- a/src/box/key_def.h +++ b/src/box/key_def.h @@ -311,6 +311,16 @@ struct credentials { uint32_t uid; }; +/** + * The supported language of the stored function. + */ +enum func_language { + FUNC_LANGUAGE_LUA, + FUNC_LANGUAGE_C, + func_language_MAX, +}; +extern const char *func_language_strs[]; + /** * Definition of a function. Function body is not stored * or replicated (yet). @@ -326,18 +336,11 @@ struct func_def { */ bool setuid; /** - * Authentication id of the owner of the function, - * used for set-user-id functions. + * The language of the stored function. */ - struct credentials owner_credentials; + enum func_language language; /** Function name. */ char name[BOX_NAME_MAX + 1]; - /** - * Strictly speaking, this doesn't belong - * to func def but belongs to func cache entry. - * Kept here for simplicity. - */ - struct access access[BOX_USER_MAX]; }; /** diff --git a/src/box/lua/call.cc b/src/box/lua/call.cc index 1211519a4c0465c5f0db0db41ef234c975d4a2a0..3fce6f259af60611cc6d409121668ef1c15714e7 100644 --- a/src/box/lua/call.cc +++ b/src/box/lua/call.cc @@ -50,6 +50,7 @@ #include "box/txn.h" #include "box/user_def.h" #include "box/user.h" +#include "box/func.h" #include "box/schema.h" #include "box/session.h" #include "box/iproto_constants.h" @@ -95,7 +96,7 @@ port_lua(struct port *port) { return (struct port_lua *) port; } * @sa port_add_lua_multret */ -static void +extern "C" void port_lua_add_tuple(struct port *port, struct tuple *tuple) { lua_State *L = port_lua(port)->L; @@ -439,7 +440,6 @@ box_lua_find(lua_State *L, const char *name, const char *name_end) return 1 + objstack; } - /** * A helper to find lua stored procedures for box.call. * box.call iteslf is pure Lua, to avoid issues @@ -468,12 +468,12 @@ struct SetuidGuard struct credentials *orig_credentials; inline SetuidGuard(const char *name, uint32_t name_len, - uint8_t access); + uint8_t access, struct func *func); inline ~SetuidGuard(); }; SetuidGuard::SetuidGuard(const char *name, uint32_t name_len, - uint8_t access) + uint8_t access, struct func *func) :setuid(false) ,orig_credentials(current_user()) { @@ -486,22 +486,14 @@ SetuidGuard::SetuidGuard(const char *name, uint32_t name_len, if ((orig_credentials->universal_access & PRIV_ALL) == PRIV_ALL) return; access &= ~orig_credentials->universal_access; - /* - * We need to look up the function by name even if - * the user has access to it, since it could require - * a set of other user id. - */ - struct func_def *func = func_by_name(name, name_len); if (func == NULL && access == 0) { /** * Well, the function is not explicitly defined, - * so it's obviously not a setuid one. Wasted - * cycles on look up while the user had universal - * access :( + * so it's obviously not a setuid one. */ return; } - if (func == NULL || (func->uid != orig_credentials->uid && + if (func == NULL || (func->def.uid != orig_credentials->uid && access & ~func->access[orig_credentials->auth_token].effective)) { /* Access violation, report error. */ char name_buf[BOX_NAME_MAX + 1]; @@ -511,16 +503,16 @@ SetuidGuard::SetuidGuard(const char *name, uint32_t name_len, tnt_raise(ClientError, ER_FUNCTION_ACCESS_DENIED, priv_name(access), user->name, name_buf); } - if (func->setuid) { + if (func->def.setuid) { /** 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_def is created, no user may + * when func is created, no user may * be around to fill it (recovery of * system spaces from a snapshot). */ - struct user *owner = user_cache_find(func->uid); + struct user *owner = user_cache_find(func->def.uid); credentials_init(&func->owner_credentials, owner); } setuid = true; @@ -564,9 +556,19 @@ execute_call(lua_State *L, struct request *request, struct obuf *out) { const char *name = request->key; uint32_t name_len = mp_decode_strl(&name); + int oc = 0; /* how many objects are on stack after box_lua_find */ - /* Try to find a function by name */ - int oc = box_lua_find(L, name, name + name_len); + struct func *func = func_by_name(name, name_len); + /* + * func == NULL means that perhaps the user has a global + * "EXECUTE" privilege, so no specific grant to a function. + */ + if (func == NULL || func->def.language == FUNC_LANGUAGE_LUA) { + /* Try to find a function by name in Lua */ + oc = box_lua_find(L, name, name + name_len); + } else if (func->func == NULL) { + func_load(func); + } /** * Check access to the function and optionally change * execution time user id (set user id). Sic: the order @@ -574,17 +576,24 @@ execute_call(lua_State *L, struct request *request, struct obuf *out) * https://github.com/tarantool/tarantool/issues/300 * - if a function does not exist, say it first. */ - SetuidGuard setuid(name, name_len, PRIV_X); + SetuidGuard setuid(name, name_len, PRIV_X, func); /* Push the rest of args (a tuple). */ const char *args = request->tuple; - 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); + if (func == NULL || func->def.language == FUNC_LANGUAGE_LUA) { + 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); + } else { + struct port_lua port; + port_lua_create(&port, L); + + func->func(request, (struct port *) &port); + } /** * Add all elements from Lua stack to iproto. * diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua index 5461756fb9f412737173b95b52793611e77aae36..d5eb948d49eed7b6e3b970878d9940b78ecaaeb5 100644 --- a/src/box/lua/schema.lua +++ b/src/box/lua/schema.lua @@ -976,7 +976,9 @@ end box.schema.func = {} box.schema.func.create = function(name, opts) opts = opts or {} - check_param_table(opts, { setuid = 'boolean', if_not_exists = 'boolean' }) + check_param_table(opts, { setuid = 'boolean', + if_not_exists = 'boolean', + language = 'string'}) local _func = box.space[box.schema.FUNC_ID] local func = _func.index.name:get{name} if func then @@ -985,9 +987,9 @@ box.schema.func.create = function(name, opts) end return end - opts = update_param_table(opts, { setuid = false }) + opts = update_param_table(opts, { setuid = false, type = 'lua'}) opts.setuid = opts.setuid and 1 or 0 - _func:auto_increment{session.uid(), name, opts.setuid} + _func:auto_increment{session.uid(), name, opts.setuid, opts.language} end box.schema.func.drop = function(name, opts) diff --git a/src/box/port.cc b/src/box/port.c similarity index 100% rename from src/box/port.cc rename to src/box/port.c diff --git a/src/box/port.h b/src/box/port.h index 776b35d06a6415d3330bb708f7e328f957d112ab..e839114876127ea8ea9796e112d7e741c884d4ca 100644 --- a/src/box/port.h +++ b/src/box/port.h @@ -30,6 +30,10 @@ */ #include "trivia/util.h" +#if defined(__cplusplus) +extern "C" { +#endif /* defined(__cplusplus) */ + struct tuple; struct port; @@ -77,14 +81,18 @@ port_add_tuple(struct port *port, struct tuple *tuple) (port->vtab->add_tuple)(port, tuple); } -/** Reused in port_lua */ -void -null_port_eof(struct port *port __attribute__((unused))); - /** * This one does not have state currently, thus a single * instance is sufficient. */ extern struct port null_port; +/** Reused in port_lua */ +void +null_port_eof(struct port *port __attribute__((unused))); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif /* defined(__cplusplus) */ + #endif /* INCLUDES_TARANTOOL_BOX_PORT_H */ diff --git a/src/box/schema.cc b/src/box/schema.cc index ed5f07283865f0d7e570e9520be734dde78463c3..04203b546676f190b4bf34858aa536d9173472e2 100644 --- a/src/box/schema.cc +++ b/src/box/schema.cc @@ -30,6 +30,7 @@ #include "user_def.h" #include "engine.h" #include "space.h" +#include "func.h" #include "tuple.h" #include "assoc.h" #include "lua/utils.h" @@ -313,35 +314,35 @@ schema_free(void) while (mh_size(funcs) > 0) { mh_int_t i = mh_first(funcs); - struct func_def *func = (struct func_def *) - mh_i32ptr_node(funcs, i)->val; - func_cache_delete(func->fid); + struct func *func = ((struct func *) + mh_i32ptr_node(funcs, i)->val); + func_cache_delete(func->def.fid); } mh_i32ptr_delete(funcs); } void -func_cache_replace(struct func_def *func) +func_cache_replace(struct func_def *def) { - struct func_def *old = func_by_id(func->fid); + struct func *old = func_by_id(def->fid); if (old) { - *old = *func; + func_update(old, def); return; } if (mh_size(funcs) >= BOX_FUNCTION_MAX) tnt_raise(ClientError, ER_FUNCTION_MAX, BOX_FUNCTION_MAX); - void *ptr = malloc(sizeof(*func)); - if (ptr == NULL) { + struct func *func = func_new(def); + if (func == NULL) { error: panic_syserror("Out of memory for the data " - "dictionary cache."); + "dictionary cache (stored function)."); } - memcpy(ptr, func, sizeof(*func)); - func = (struct func_def *) ptr; - const struct mh_i32ptr_node_t node = { func->fid, func }; + const struct mh_i32ptr_node_t node = { def->fid, func }; mh_int_t k = mh_i32ptr_put(funcs, &node, NULL, NULL); - if (k == mh_end(funcs)) + if (k == mh_end(funcs)) { + func_delete(func); goto error; + } } void @@ -350,19 +351,19 @@ func_cache_delete(uint32_t fid) mh_int_t k = mh_i32ptr_find(funcs, fid, NULL); if (k == mh_end(funcs)) return; - struct func_def *func = (struct func_def *) + struct func *func = (struct func *) mh_i32ptr_node(funcs, k)->val; mh_i32ptr_del(funcs, k, NULL); - free(func); + func_delete(func); } -struct func_def * +struct func * func_by_id(uint32_t fid) { mh_int_t func = mh_i32ptr_find(funcs, fid, NULL); if (func == mh_end(funcs)) return NULL; - return (struct func_def *) mh_i32ptr_node(funcs, func)->val; + return (struct func *) mh_i32ptr_node(funcs, func)->val; } bool diff --git a/src/box/schema.h b/src/box/schema.h index e321deb518cf8e0562a536b1cd0d4ad4db65c6d2..93d6076d036c2a4269e287eeefebe6d8ca5b22fb 100644 --- a/src/box/schema.h +++ b/src/box/schema.h @@ -120,30 +120,33 @@ schema_find_id(uint32_t system_space_id, uint32_t index_id, const char *name, uint32_t len); void -func_cache_replace(struct func_def *func); +func_cache_replace(struct func_def *def); void func_cache_delete(uint32_t fid); -struct func_def * +struct func; + +struct func * func_by_id(uint32_t fid); -static inline struct func_def * +static inline struct func * func_cache_find(uint32_t fid) { - struct func_def *func = func_by_id(fid); + struct func *func = func_by_id(fid); if (func == NULL) tnt_raise(ClientError, ER_NO_SUCH_FUNCTION, int2str(fid)); return func; } -static inline struct func_def * +static inline struct func * func_by_name(const char *name, uint32_t name_len) { uint32_t fid = schema_find_id(SC_FUNC_ID, 2, name, name_len); return func_by_id(fid); } + /** * Check whether or not an object has grants on it (restrict * constraint in drop object). diff --git a/src/box/sysview_index.cc b/src/box/sysview_index.cc index 111ef64bef89ef2e6c23fc3a26841c9f6e10645e..6d70bc1ba6567bd6fd5ad3ff98e78645cd4765cd 100644 --- a/src/box/sysview_index.cc +++ b/src/box/sysview_index.cc @@ -29,6 +29,7 @@ #include "sysview_index.h" #include "schema.h" #include "space.h" +#include "func.h" #include "tuple.h" #include "session.h" @@ -224,10 +225,10 @@ vfunc_filter(struct space *source, struct tuple *tuple) const char *name = tuple_field_cstr(tuple, 2); uint32_t name_len = strlen(name); - struct func_def *func = func_by_name(name, name_len); + struct func *func = func_by_name(name, name_len); assert(func != NULL); uint8_t effective = func->access[cr->auth_token].effective; - if (func->uid == cr->uid || (PRIV_X & effective)) + if (func->def.uid == cr->uid || (PRIV_X & effective)) return true; return false; } diff --git a/src/box/tuple.h b/src/box/tuple.h index c16d36d87d7d4d126c51c9e18728b97914ef2fed..130def0ab3115dc1dd4eff2731f17763dda358c1 100644 --- a/src/box/tuple.h +++ b/src/box/tuple.h @@ -177,11 +177,9 @@ struct tuple * tuple_alloc(struct tuple_format *format, size_t size); /** - * Create a new tuple from a sequence of BER-len encoded fields. + * Create a new tuple from a sequence of MsgPack encoded fields. * tuple->refs is 0. * - * @post *data is advanced to the length of tuple data - * * Throws an exception if tuple format is incorrect. */ struct tuple * diff --git a/src/box/user.cc b/src/box/user.cc index c08dced2abde7329669de6d2f0133d571f3f5053..1042c8ba14f7352b66e931b34c4454af37e245c5 100644 --- a/src/box/user.cc +++ b/src/box/user.cc @@ -31,6 +31,7 @@ #include "assoc.h" #include "schema.h" #include "space.h" +#include "func.h" #include "index.h" #include "bit/bit.h" #include "fiber.h" @@ -213,7 +214,7 @@ access_find(struct priv_def *priv) } case SC_FUNCTION: { - struct func_def *func = func_by_id(priv->object_id); + struct func *func = func_by_id(priv->object_id); if (func) access = func->access; break; diff --git a/src/ffisyms.cc b/src/ffisyms.cc index ffa296180b36fa8558c9b51892f7984c14619150..068cc861e60581ecb41f498d0c1f34c15685d004 100644 --- a/src/ffisyms.cc +++ b/src/ffisyms.cc @@ -1,4 +1,3 @@ - #include <bit/bit.h> #include <lib/msgpuck/msgpuck.h> #include "scramble.h" @@ -66,5 +65,5 @@ void *ffi_symbols[] = { (void *) random_bytes, (void *) fiber_time, (void *) fiber_time64, - (void *) sophia_schedule + (void *) sophia_schedule, }; diff --git a/src/trivia/CMakeLists.txt b/src/trivia/CMakeLists.txt index 4a50400da68c92377432a065dea36c91a16a523c..e81b4a585b886d5a0ec2958bf5e128984869262b 100644 --- a/src/trivia/CMakeLists.txt +++ b/src/trivia/CMakeLists.txt @@ -1,6 +1,7 @@ set(api_headers ${CMAKE_CURRENT_BINARY_DIR}/config.h - ../say.h - ../coeio.h - ../lua/utils.h) -apigen(${api_headers}) + ${CMAKE_SOURCE_DIR}/src/say.h + ${CMAKE_SOURCE_DIR}/src/coeio.h + ${CMAKE_SOURCE_DIR}/src/lua/utils.h + ${CMAKE_SOURCE_DIR}/src/box/func.h) +rebuild_module_api(${api_headers}) diff --git a/test-run b/test-run index 7a9dffa16d0da05ec33fe0b131e92f6d86a87fd6..6b4dcd67126709a92e722a62c8a87a6ec90ceb0f 160000 --- a/test-run +++ b/test-run @@ -1 +1 @@ -Subproject commit 7a9dffa16d0da05ec33fe0b131e92f6d86a87fd6 +Subproject commit 6b4dcd67126709a92e722a62c8a87a6ec90ceb0f diff --git a/test/app/CMakeLists.txt b/test/app/CMakeLists.txt index 3b553fd30d5055fbdffabc5cc15543469a9c0f6d..804698325c69722700810776a13eda74cb01b7ea 100644 --- a/test/app/CMakeLists.txt +++ b/test/app/CMakeLists.txt @@ -1,7 +1,14 @@ include_directories(${CMAKE_BINARY_DIR}/src/trivia) -add_library(module_api SHARED module_api.c) -set_target_properties(module_api PROPERTIES PREFIX "") -if(TARGET_OS_DARWIN) - set_target_properties(module_api PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") -endif(TARGET_OS_DARWIN) -add_dependencies(module_api generate_module_api) + +function(build_module module files) + add_library(${module} SHARED ${files}) + set_target_properties(${module} PROPERTIES PREFIX "") + add_dependencies(${module} rebuild_module_api) + if(TARGET_OS_DARWIN) + set_target_properties(${module} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") + endif(TARGET_OS_DARWIN) +endfunction() + + +build_module(module_api module_api.c) +build_module(function1 function1.c) diff --git a/test/app/function1.c b/test/app/function1.c new file mode 100644 index 0000000000000000000000000000000000000000..b73e500823a27fd7398fd08b153020198ea630da --- /dev/null +++ b/test/app/function1.c @@ -0,0 +1,8 @@ +#include "tarantool.h" + +int +function1(struct request *request, struct port *port) +{ + say_info("-- function1 - called --"); + return 0; +} diff --git a/test/app/function1.result b/test/app/function1.result new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/test/app/function1.test.lua b/test/app/function1.test.lua new file mode 100755 index 0000000000000000000000000000000000000000..f84ff151a4e5bda83b1701ce0c1a095adbb2e412 --- /dev/null +++ b/test/app/function1.test.lua @@ -0,0 +1,22 @@ +#!/usr/bin/env tarantool + +box.cfg{ + listen = os.getenv("LISTEN"), + slab_alloc_arena = 0.1, + pid_file = "tarantool.pid", + rows_per_wal = 50, + logger = "tarantool.log" +} + +package.cpath = '../app/?.so;../app/?.dylib;'..package.cpath + +log = require('log') +net = require('net.box') + +box.schema.func.create('function1', {language = "C"}) +box.schema.user.grant('guest', 'execute', 'function', 'function1') + +c = net:new(os.getenv("LISTEN")) +c:call('function1') + +os.exit(0) diff --git a/test/app/init_script.result b/test/app/init_script.result index 072e1608910b0fad8b5102310ece2fbfb4737a36..19e35675f491b547ba0f300fe4e650a0e863e979 100644 --- a/test/app/init_script.result +++ b/test/app/init_script.result @@ -10,7 +10,7 @@ box.cfg 5 background:false 6 logger:tarantool.log 7 slab_alloc_arena:0.1 -8 listen:3314 +8 listen:port 9 logger_nonblock:true 10 snap_dir:. 11 coredump:false diff --git a/test/app/init_script.test.lua b/test/app/init_script.test.lua index d79c24e754398471cc021ca34c5852d44cf3968a..8c14ec86ee552128c06bdb8586dfef7a3b890308 100755 --- a/test/app/init_script.test.lua +++ b/test/app/init_script.test.lua @@ -3,7 +3,7 @@ -- Testing init script -- box.cfg{ - listen = 3314, + listen = os.getenv("LISTEN"), pid_file = "box.pid", slab_alloc_arena=0.1, logger="tarantool.log" @@ -26,7 +26,14 @@ print[[ ]] t = {} -for k,v in pairs(box.cfg) do if type(v) ~= 'table' and type(v) ~= 'function' then table.insert(t,k..':'..tostring(v)) end end +for k,v in pairs(box.cfg) do + if k == 'listen' then + v = 'port' + end + if type(v) ~= 'table' and type(v) ~= 'function' then + table.insert(t,k..':'..tostring(v)) + end +end print('box.cfg') for k,v in pairs(t) do print(k, v) end diff --git a/test/app/snapshot.test.lua b/test/app/snapshot.test.lua index 228fa058d3079ccd784c6fbefd32e252d8ccc468..c98cbf9a16b381852d202c34039bb2e8359bb3c0 100755 --- a/test/app/snapshot.test.lua +++ b/test/app/snapshot.test.lua @@ -27,6 +27,8 @@ function purge() end end +continue_snapshoting = true + function snapshot(lsn) fiber.name('snapshot') while true do @@ -35,8 +37,12 @@ function snapshot(lsn) lsn = new_lsn; box.snapshot() end + if not continue_snapshoting then + break + end fiber.sleep(0.001) end + snap_chan:put("!") end box.cfg{logger="tarantool.log", slab_alloc_arena=0.1, rows_per_wal=5000} @@ -55,8 +61,11 @@ fiber.create(noise) fiber.create(purge) fiber.create(noise) fiber.create(purge) -fiber.create(snapshot, box.info.server.lsn) +snap_chan = fiber.channel() +snap_fib = fiber.create(snapshot, box.info.server.lsn) fiber.sleep(0.3) +continue_snapshoting = false +snap_chan:get() print('ok') os.exit(0) diff --git a/test/box/misc.result b/test/box/misc.result index b937e97b0df73669d1c7ad86f677cf1373d02baa..2dd1329d52d3bdf025a153863ce47d15b26837d7 100644 --- a/test/box/misc.result +++ b/test/box/misc.result @@ -252,6 +252,8 @@ t; - 'box.error.TUPLE_NOT_FOUND : 4' - 'box.error.DROP_USER : 44' - 'box.error.CROSS_ENGINE_TRANSACTION : 81' + - 'box.error.injection : table: <address> + - 'box.error.FUNCTION_LANGUAGE : 100' - 'box.error.MODIFY_INDEX : 14' - 'box.error.TUPLE_FOUND : 3' - 'box.error.FIELD_TYPE : 23' @@ -279,7 +281,7 @@ t; - 'box.error.ROLE_EXISTS : 83' - 'box.error.NO_SUCH_ROLE : 82' - 'box.error.NO_ACTIVE_TRANSACTION : 80' - - 'box.error.injection : table: <address> + - 'box.error.LOAD_FUNCTION : 99' - 'box.error.FIELD_TYPE_MISMATCH : 24' - 'box.error.UNSUPPORTED : 5' - 'box.error.INVALID_MSGPACK : 20' diff --git a/test/module/box.lua b/test/module/box.lua deleted file mode 100644 index c17a78000894e2eb4d1da36d0fb507a0c5a793f6..0000000000000000000000000000000000000000 --- a/test/module/box.lua +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env tarantool - -box.cfg{ - listen = os.getenv("LISTEN"), - slab_alloc_arena = 0.1, - pid_file = "tarantool.pid", - rows_per_wal = 50 -} - -require('console').listen(os.getenv('ADMIN')) diff --git a/test/module/suite.ini b/test/module/suite.ini deleted file mode 100644 index afca8175f19d31be093c3346cc078cac507472ba..0000000000000000000000000000000000000000 --- a/test/module/suite.ini +++ /dev/null @@ -1,4 +0,0 @@ -[default] -script = box.lua -disabled = -description = tarantool/box, optional lua modules diff --git a/test/wal_off/snapshot_stress.result b/test/wal_off/snapshot_stress.result index e136206758b7f57cedad15be7088f9c17311900e..47f90d56121bd9403d3f63aa0301ca18c0b2d45c 100644 --- a/test/wal_off/snapshot_stress.result +++ b/test/wal_off/snapshot_stress.result @@ -105,6 +105,15 @@ snaps = fio.glob(snap_search_wildcard); initial_snap_count = #snaps --- ... +if box.space.accounts then box.space.accounts:drop() end +--- +... +if box.space.operations then box.space.operations:drop() end +--- +... +if box.space.deleting then box.space.deleting:drop() end +--- +... s1 = box.schema.create_space("accounts") --- ... @@ -117,6 +126,12 @@ s2 = box.schema.create_space("operations") i2 = s2:create_index('primary', { type = 'HASH', parts = {1, 'num'} }) --- ... +s3 = box.schema.create_space("deleting") +--- +... +i3 = s3:create_index('primary', { type = 'TREE', parts = {1, 'num'} }) +--- +... n_accs = 0 --- ... @@ -145,6 +160,11 @@ function get_new_space_name() end; --- ... +tmp = get_new_space_name() +if box.space[tmp] then box.space[tmp]:drop() tmp = get_new_space_name() end +tmp = nil +n_spaces = 0 + function get_rnd_acc() return math.floor(math.random() * n_accs) end; @@ -201,10 +221,18 @@ function do_rand_op() end; --- ... +function remove_smth() + s3:delete{i3:min()[1]} +end; +--- +... function init() for i = 1,accounts_start do add_acc() end + for i = 1,workers_count*iteration_count do + s3:auto_increment{"I hate dentists!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"} + end end; --- ... @@ -214,6 +242,7 @@ function work_itr() fiber.sleep(0) end add_acc() + remove_smth() add_space() end; --- diff --git a/test/wal_off/snapshot_stress.test.lua b/test/wal_off/snapshot_stress.test.lua index 4b3c045d480b59e31f0b8c6629c6546c804247f5..8e0d4cffbdd6e39b78d8becca753967fe93c27f6 100644 --- a/test/wal_off/snapshot_stress.test.lua +++ b/test/wal_off/snapshot_stress.test.lua @@ -45,10 +45,16 @@ snap_search_wildcard = fio.pathjoin(box.cfg.snap_dir, '*.snap'); snaps = fio.glob(snap_search_wildcard); initial_snap_count = #snaps +if box.space.accounts then box.space.accounts:drop() end +if box.space.operations then box.space.operations:drop() end +if box.space.deleting then box.space.deleting:drop() end + s1 = box.schema.create_space("accounts") i1 = s1:create_index('primary', { type = 'HASH', parts = {1, 'num'} }) s2 = box.schema.create_space("operations") i2 = s2:create_index('primary', { type = 'HASH', parts = {1, 'num'} }) +s3 = box.schema.create_space("deleting") +i3 = s3:create_index('primary', { type = 'TREE', parts = {1, 'num'} }) n_accs = 0 n_ops = 0 @@ -67,6 +73,11 @@ function get_new_space_name() return "test" .. tostring(n_spaces - 1) end; +tmp = get_new_space_name() +if box.space[tmp] then box.space[tmp]:drop() tmp = get_new_space_name() end +tmp = nil +n_spaces = 0 + function get_rnd_acc() return math.floor(math.random() * n_accs) end; @@ -113,10 +124,17 @@ function do_rand_op() do_op(get_rnd_acc(), get_rnd_acc(), get_rnd_val()) end; +function remove_smth() + s3:delete{i3:min()[1]} +end; + function init() for i = 1,accounts_start do add_acc() end + for i = 1,workers_count*iteration_count do + s3:auto_increment{"I hate dentists!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"} + end end; function work_itr() @@ -125,6 +143,7 @@ function work_itr() fiber.sleep(0) end add_acc() + remove_smth() add_space() end; diff --git a/third_party/libev/ev.c b/third_party/libev/ev.c index 34a30b6f5018fa1b6f613754abb477ece135b373..083c9f6c10c2a3357319b424c68755c0044e9beb 100644 --- a/third_party/libev/ev.c +++ b/third_party/libev/ev.c @@ -2225,7 +2225,7 @@ ev_recommended_backends (void) EV_THROW { unsigned int flags = ev_supported_backends (); -#ifndef __NetBSD__ +#if !defined(__NetBSD__) && !defined(__FreeBSD__) /* kqueue is borked on everything but netbsd apparently */ /* it usually doesn't work correctly on anything but sockets and pipes */ flags &= ~EVBACKEND_KQUEUE;