summaryrefslogtreecommitdiffstats
path: root/src/lua/lua_mempool.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lua/lua_mempool.c612
1 files changed, 612 insertions, 0 deletions
diff --git a/src/lua/lua_mempool.c b/src/lua/lua_mempool.c
new file mode 100644
index 0000000..4897d15
--- /dev/null
+++ b/src/lua/lua_mempool.c
@@ -0,0 +1,612 @@
+/*-
+ * 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);
+/**
+ * @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;
+ 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, -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 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_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 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);
+}