/* * Copyright 2024 Vsevolod Stakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "lua_common.h" /*** * @module rspamd_mempool * Rspamd memory pool is used to allocate memory attached to specific objects, * namely it was initially used for memory allocation for rspamd_task. * * All memory allocated by the pool is destroyed when the associated object is * destroyed. This allows a sort of controlled garbage collection for memory * allocated from the pool. Memory pools are extensively used by rspamd internal * components and provide some powerful features, such as destructors or * persistent variables. * @example local mempool = require "rspamd_mempool" local pool = mempool.create() pool:set_variable('a', 'bcd', 1, 1.01, false) local v1, v2, v3, v4 = pool:get_variable('a', 'string,double,double,bool') pool:destroy() */ /* Lua bindings */ /*** * @function mempool.create([size]) * Creates a memory pool of a specified `size` or platform dependent optimal size (normally, a page size) * @param {number} size size of a page inside pool * @return {rspamd_mempool} new pool object (that should be removed by explicit call to `pool:destroy()`) */ LUA_FUNCTION_DEF(mempool, create); /*** * @method mempool:add_destructor(func) * Adds new destructor function to the pool * @param {function} func function to be called when the pool is destroyed */ LUA_FUNCTION_DEF(mempool, add_destructor); /*** * @method mempool:destroy() * Destroys memory pool cleaning all variables and calling all destructors registered (both C and Lua ones) */ LUA_FUNCTION_DEF(mempool, delete); LUA_FUNCTION_DEF(mempool, stat); LUA_FUNCTION_DEF(mempool, suggest_size); /*** * @method mempool:set_variable(name, [value1[, value2 ...]]) * Sets a variable that's valid during memory pool lifetime. This function allows * to pack multiple values inside a single variable. Currently supported types are: * * - `string`: packed as null terminated C string (so no `\0` are allowed) * - `number`: packed as C double * - `boolean`: packed as bool * @param {string} name variable's name to set */ LUA_FUNCTION_DEF(mempool, set_variable); /*** * @method mempool:set_bucket(name, num_values, [value1...valuen]|[table]) * Stores a variable bucket of numbers where the first number is number of elements to pack * and then there should be either n numeric values or a plain table of numeric values * @param {string} name variable's name to set * @param {number} num_values number of variables in the bucket * @param {table|list} values values */ LUA_FUNCTION_DEF(mempool, set_bucket); /*** * @method mempool:get_variable(name[, type]) * Unpacks mempool variable to lua If `type` is not specified, then a variable is * assumed to be zero-terminated C string. Otherwise, `type` is a comma separated (spaces are ignored) * list of types that should be unpacked from a variable's content. The following types * are supported: * * - `string`: null terminated C string (so no `\0` are allowed) * - `double`: returned as lua number * - `int`: unpack a single integer * - `int64`: unpack 64-bits integer * - `boolean`: unpack boolean * - `bucket`: bucket of numbers represented as a Lua table * - `fstrings`: list of rspamd_fstring_t (GList) represented as a Lua table * @param {string} name variable's name to get * @param {string} type list of types to be extracted * @return {variable list} list of variables extracted (but **not** a table) */ LUA_FUNCTION_DEF(mempool, get_variable); /*** * @method mempool:has_variable(name) * Checks if the specified variable `name` exists in the memory pool * @param {string} name variable's name to get * @return {boolean} `true` if variable exists and `false` otherwise */ LUA_FUNCTION_DEF(mempool, has_variable); /*** * @method mempool:delete_variable(name) * Removes the specified variable `name` from the memory pool * @param {string} name variable's name to remove * @return {boolean} `true` if variable exists and has been removed */ LUA_FUNCTION_DEF(mempool, delete_variable); /** * @method mempool:topointer() * * Returns raw C pointer (lightuserdata) associated with mempool. This might be * broken with luajit and GC64, use with caution. */ LUA_FUNCTION_DEF(mempool, topointer); static const struct luaL_reg mempoollib_m[] = { LUA_INTERFACE_DEF(mempool, add_destructor), LUA_INTERFACE_DEF(mempool, stat), LUA_INTERFACE_DEF(mempool, suggest_size), LUA_INTERFACE_DEF(mempool, set_variable), LUA_INTERFACE_DEF(mempool, set_bucket), LUA_INTERFACE_DEF(mempool, get_variable), LUA_INTERFACE_DEF(mempool, has_variable), LUA_INTERFACE_DEF(mempool, delete_variable), LUA_INTERFACE_DEF(mempool, topointer), LUA_INTERFACE_DEF(mempool, delete), {"destroy", lua_mempool_delete}, {"__tostring", rspamd_lua_class_tostring}, {NULL, NULL}}; static const struct luaL_reg mempoollib_f[] = { LUA_INTERFACE_DEF(mempool, create), {NULL, NULL}}; /* * Struct for lua destructor */ struct lua_mempool_udata { lua_State *L; int cbref; rspamd_mempool_t *mempool; }; struct memory_pool_s * rspamd_lua_check_mempool(lua_State *L, int pos) { void *ud = rspamd_lua_check_udata(L, pos, rspamd_mempool_classname); luaL_argcheck(L, ud != NULL, pos, "'mempool' expected"); return ud ? *((struct memory_pool_s **) ud) : NULL; } static int lua_mempool_create(lua_State *L) { LUA_TRACE_POINT; struct memory_pool_s *mempool = rspamd_mempool_new( rspamd_mempool_suggest_size(), "lua", 0), **pmempool; if (mempool) { pmempool = lua_newuserdata(L, sizeof(struct memory_pool_s *)); rspamd_lua_setclass(L, rspamd_mempool_classname, -1); *pmempool = mempool; } else { lua_pushnil(L); } return 1; } static void lua_mempool_destructor_func(gpointer p) { struct lua_mempool_udata *ud = p; lua_rawgeti(ud->L, LUA_REGISTRYINDEX, ud->cbref); if (lua_pcall(ud->L, 0, 0, 0) != 0) { msg_info("call to destructor failed: %s", lua_tostring(ud->L, -1)); lua_pop(ud->L, 1); } luaL_unref(ud->L, LUA_REGISTRYINDEX, ud->cbref); } static int lua_mempool_add_destructor(lua_State *L) { LUA_TRACE_POINT; struct memory_pool_s *mempool = rspamd_lua_check_mempool(L, 1); struct lua_mempool_udata *ud; if (mempool) { if (lua_isfunction(L, 2)) { ud = rspamd_mempool_alloc(mempool, sizeof(struct lua_mempool_udata)); lua_pushvalue(L, 2); /* Get a reference */ ud->cbref = luaL_ref(L, LUA_REGISTRYINDEX); ud->L = L; ud->mempool = mempool; rspamd_mempool_add_destructor(mempool, lua_mempool_destructor_func, ud); } else { msg_err("trying to add destructor without function"); } } else { lua_pushnil(L); } return 1; } static int lua_mempool_delete(lua_State *L) { LUA_TRACE_POINT; struct memory_pool_s *mempool = rspamd_lua_check_mempool(L, 1); if (mempool) { rspamd_mempool_delete(mempool); return 0; } else { lua_pushnil(L); } return 1; } static int lua_mempool_stat(lua_State *L) { LUA_TRACE_POINT; struct memory_pool_s *mempool = rspamd_lua_check_mempool(L, 1); if (mempool) { } else { lua_pushnil(L); } return 1; } static int lua_mempool_suggest_size(lua_State *L) { LUA_TRACE_POINT; struct memory_pool_s *mempool = rspamd_lua_check_mempool(L, 1); if (mempool) { lua_pushinteger(L, rspamd_mempool_suggest_size()); return 0; } else { lua_pushnil(L); } return 1; } struct lua_numbers_bucket { unsigned int nelts; double elts[0]; }; static int lua_mempool_set_bucket(lua_State *L) { LUA_TRACE_POINT; struct memory_pool_s *mempool = rspamd_lua_check_mempool(L, 1); const char *var = luaL_checkstring(L, 2); struct lua_numbers_bucket *bucket; int nelts = luaL_checknumber(L, 3), i; if (var && nelts > 0) { bucket = rspamd_mempool_alloc(mempool, sizeof(*bucket) + sizeof(double) * nelts); bucket->nelts = nelts; if (lua_type(L, 4) == LUA_TTABLE) { /* Table version */ for (i = 1; i <= nelts; i++) { lua_rawgeti(L, 4, i); bucket->elts[i - 1] = lua_tonumber(L, -1); lua_pop(L, 1); } } else { for (i = 0; i <= nelts; i++) { bucket->elts[i] = lua_tonumber(L, 4 + i); } } rspamd_mempool_set_variable(mempool, var, bucket, NULL); } else { return luaL_error(L, "invalid arguments"); } return 0; } static int lua_mempool_set_variable(lua_State *L) { LUA_TRACE_POINT; struct memory_pool_s *mempool = rspamd_lua_check_mempool(L, 1); const char *var = luaL_checkstring(L, 2); gpointer value; struct lua_numbers_bucket *bucket; char *vp; union { double d; const char *s; gboolean b; } val; gsize slen; int i, j, len = 0, type; if (mempool && var) { for (i = 3; i <= lua_gettop(L); i++) { type = lua_type(L, i); if (type == LUA_TNUMBER) { /* We have some ambiguity here between integer and double */ len += sizeof(double); } else if (type == LUA_TBOOLEAN) { len += sizeof(gboolean); } else if (type == LUA_TSTRING) { (void) lua_tolstring(L, i, &slen); len += slen + 1; } else if (type == LUA_TTABLE) { /* We assume it as a bucket of numbers so far */ slen = rspamd_lua_table_size(L, i); len += sizeof(double) * slen + sizeof(*bucket); } else { msg_err("cannot handle lua type %s", lua_typename(L, type)); } } if (len == 0) { msg_err("no values specified"); } else { value = rspamd_mempool_alloc(mempool, len); vp = value; for (i = 3; i <= lua_gettop(L); i++) { type = lua_type(L, i); if (type == LUA_TNUMBER) { val.d = lua_tonumber(L, i); memcpy(vp, &val, sizeof(double)); vp += sizeof(double); } else if (type == LUA_TBOOLEAN) { val.b = lua_toboolean(L, i); memcpy(vp, &val, sizeof(gboolean)); vp += sizeof(gboolean); } else if (type == LUA_TSTRING) { val.s = lua_tolstring(L, i, &slen); memcpy(vp, val.s, slen + 1); vp += slen + 1; } else if (type == LUA_TTABLE) { slen = rspamd_lua_table_size(L, i); /* XXX: Ret, ret, ret: alignment issues */ bucket = (struct lua_numbers_bucket *) vp; bucket->nelts = slen; for (j = 0; j < slen; j++) { lua_rawgeti(L, i, j + 1); bucket->elts[j] = lua_tonumber(L, -1); lua_pop(L, 1); } vp += sizeof(double) * slen + sizeof(*bucket); } else { msg_err("cannot handle lua type %s", lua_typename(L, type)); } } rspamd_mempool_set_variable(mempool, var, value, NULL); } return 0; } else { lua_pushnil(L); } return 1; } static int lua_mempool_get_variable(lua_State *L) { LUA_TRACE_POINT; struct memory_pool_s *mempool = rspamd_lua_check_mempool(L, 1); const char *var = luaL_checkstring(L, 2); const char *type = NULL, *pt; struct lua_numbers_bucket bucket; const char *value, *pv; unsigned int len, nvar, slen, i; if (mempool && var) { value = rspamd_mempool_get_variable(mempool, var); if (lua_gettop(L) >= 3) { type = luaL_checkstring(L, 3); } if (value) { if (type) { pt = type; pv = value; nvar = 0; while ((len = strcspn(pt, ", ")) > 0) { if (len == sizeof("double") - 1 && g_ascii_strncasecmp(pt, "double", len) == 0) { double num; memcpy(&num, pv, sizeof(double)); lua_pushnumber(L, num); pv += sizeof(double); } else if (len == sizeof("int") - 1 && g_ascii_strncasecmp(pt, "int", len) == 0) { int num; memcpy(&num, pv, sizeof(int)); lua_pushinteger(L, num); pv += sizeof(int); } else if (len == sizeof("int64") - 1 && g_ascii_strncasecmp(pt, "int64", len) == 0) { int64_t num; memcpy(&num, pv, sizeof(int64_t)); lua_pushinteger(L, num); pv += sizeof(int64_t); } else if (len == sizeof("bool") - 1 && g_ascii_strncasecmp(pt, "bool", len) == 0) { gboolean num; memcpy(&num, pv, sizeof(gboolean)); lua_pushboolean(L, num); pv += sizeof(gboolean); } else if (len == sizeof("string") - 1 && g_ascii_strncasecmp(pt, "string", len) == 0) { slen = strlen((const char *) pv); lua_pushlstring(L, (const char *) pv, slen); pv += slen + 1; } else if (len == sizeof("gstring") - 1 && g_ascii_strncasecmp(pt, "gstring", len) == 0) { GString *st = (GString *) pv; lua_pushlstring(L, st->str, st->len); pv += sizeof(GString *); } else if (len == sizeof("bucket") - 1 && g_ascii_strncasecmp(pt, "bucket", len) == 0) { memcpy(&bucket, pv, sizeof(bucket)); lua_createtable(L, bucket.nelts, 0); pv += sizeof(struct lua_numbers_bucket); for (i = 0; i < bucket.nelts; i++) { double num; memcpy(&num, pv, sizeof(num)); lua_pushnumber(L, num); lua_rawseti(L, -2, i + 1); pv += sizeof(num); } } else if (len == sizeof("fstrings") - 1 && g_ascii_strncasecmp(pt, "fstrings", len) == 0) { GList *cur; rspamd_fstring_t *fstr; cur = (GList *) pv; lua_newtable(L); i = 1; while (cur != NULL) { fstr = cur->data; lua_pushlstring(L, fstr->str, fstr->len); lua_rawseti(L, -2, i); i++; cur = g_list_next(cur); } pv += sizeof(GList *); } else { msg_err("unknown type for get_variable: %s", pt); lua_pushnil(L); } pt += len; pt += strspn(pt, ", "); nvar++; } return nvar; } else { /* No type specified, return string */ lua_pushstring(L, value); } } else { lua_pushnil(L); } } else { lua_pushnil(L); } return 1; } static int lua_mempool_has_variable(lua_State *L) { LUA_TRACE_POINT; struct memory_pool_s *mempool = rspamd_lua_check_mempool(L, 1); const char *var = luaL_checkstring(L, 2); gboolean ret = FALSE; if (mempool && var) { if (rspamd_mempool_get_variable(mempool, var) != NULL) { ret = TRUE; } } lua_pushboolean(L, ret); return 1; } static int lua_mempool_delete_variable(lua_State *L) { LUA_TRACE_POINT; struct memory_pool_s *mempool = rspamd_lua_check_mempool(L, 1); const char *var = luaL_checkstring(L, 2); gboolean ret = FALSE; if (mempool && var) { if (rspamd_mempool_get_variable(mempool, var) != NULL) { ret = TRUE; rspamd_mempool_remove_variable(mempool, var); } } lua_pushboolean(L, ret); return 1; } static int lua_mempool_topointer(lua_State *L) { LUA_TRACE_POINT; rspamd_mempool_t *pool = rspamd_lua_check_mempool(L, 1); if (pool) { /* XXX: this might cause issues on arm64 and LuaJIT */ lua_pushlightuserdata(L, pool); } else { return luaL_error(L, "invalid arguments"); } return 1; } static int lua_load_mempool(lua_State *L) { lua_newtable(L); luaL_register(L, NULL, mempoollib_f); return 1; } void luaopen_mempool(lua_State *L) { rspamd_lua_new_class(L, rspamd_mempool_classname, mempoollib_m); lua_pop(L, 1); rspamd_lua_add_preload(L, "rspamd_mempool", lua_load_mempool); }