/*- * Copyright 2016 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); 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, 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; gint cbref; rspamd_mempool_t *mempool; }; struct memory_pool_s * rspamd_lua_check_mempool (lua_State * L, gint pos) { void *ud = rspamd_lua_check_udata (L, pos, "rspamd{mempool}"); 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}", -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 { guint nelts; gdouble 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 gchar *var = luaL_checkstring (L, 2); struct lua_numbers_bucket *bucket; gint nelts = luaL_checknumber (L, 3), i; if (var && nelts > 0) { bucket = rspamd_mempool_alloc (mempool, sizeof (*bucket) + sizeof (gdouble) * 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 gchar *var = luaL_checkstring (L, 2); gpointer value; struct lua_numbers_bucket *bucket; gchar *vp; union { gdouble d; const gchar *s; gboolean b; } val; gsize slen; gint 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 (gdouble); } 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 (gdouble) * 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 (gdouble)); vp += sizeof (gdouble); } 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 (gdouble) * 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 gchar *var = luaL_checkstring (L, 2); const gchar *type = NULL, *pt; struct lua_numbers_bucket bucket; const gchar *value, *pv; guint 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) { gdouble num; memcpy (&num, pv, sizeof (gdouble)); lua_pushnumber (L, num); pv += sizeof (gdouble); } else if (len == sizeof ("int") - 1 && g_ascii_strncasecmp (pt, "int", len) == 0) { gint num; memcpy (&num, pv, sizeof (gint)); lua_pushinteger (L, num); pv += sizeof (gint); } else if (len == sizeof ("int64") - 1 && g_ascii_strncasecmp (pt, "int64", len) == 0) { gint64 num; memcpy (&num, pv, sizeof (gint64)); lua_pushinteger (L, num); pv += sizeof (gint64); } 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 gchar *)pv); lua_pushlstring (L, (const gchar *)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 ++) { gdouble num; memcpy (&num, pv, sizeof (num)); lua_pushnumber (L, num); lua_rawseti (L, -1, 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; } 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 gchar *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 gchar *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 gint 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}", mempoollib_m); lua_pop (L, 1); rspamd_lua_add_preload (L, "rspamd_mempool", lua_load_mempool); }