MEDIUM: lua: implement a simple memory allocator

Lua supports a memory allocator. This is very important as it's the
only way we can control the amount of memory allocatable by Lua scripts.
That avoids prevents bogus scripts from eating all of the system's memory.
The value can be enforced using tune.lua.maxmem in the global section.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 9a04200..34a1e49 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -487,6 +487,7 @@
    - tune.http.maxhdr
    - tune.idletimer
    - tune.lua.forced-yield
+   - tune.lua.maxmem
    - tune.lua.session-timeout
    - tune.lua.task-timeout
    - tune.maxaccept
@@ -999,6 +1000,12 @@
   lowered. If the Lua code is quite long and its result is absolutely required
   to process the data, the <number> can be increased.
 
+tune.lua.maxmem
+  Sets the maximum amount of RAM in megabytes per process usable by Lua. By
+  default it is zero which means unlimited. It is important to set a limit to
+  ensure that a bug in a script will not result in the system running out of
+  memory.
+
 tune.lua.session-timeout <timeout>
   This is the execution timeout for the Lua sessions. This is useful for
   preventing infinite loops or spending too much time in Lua. This timeout has a
diff --git a/src/hlua.c b/src/hlua.c
index 82058d8..1e1c114 100644
--- a/src/hlua.c
+++ b/src/hlua.c
@@ -112,6 +112,16 @@
  */
 static unsigned int hlua_nb_instruction = 10000;
 
+/* Descriptor for the memory allocation state. If limit is not null, it will
+ * be enforced on any memory allocation.
+ */
+struct hlua_mem_allocator {
+	size_t allocated;
+	size_t limit;
+};
+
+static struct hlua_mem_allocator hlua_global_allocator;
+
 /* These functions converts types between HAProxy internal args or
  * sample and LUA types. Another function permits to check if the
  * LUA stack contains arguments according with an required ARG_T
@@ -4417,6 +4427,25 @@
 	return 0;
 }
 
+static int hlua_parse_maxmem(char **args, int section_type, struct proxy *curpx,
+                             struct proxy *defpx, const char *file, int line,
+                             char **err)
+{
+	char *error;
+
+	if (*(args[1]) == 0) {
+		memprintf(err, "'%s' expects an integer argument (Lua memory size in MB).\n", args[0]);
+		return -1;
+	}
+	hlua_global_allocator.limit = strtoll(args[1], &error, 10) * 1024L * 1024L;
+	if (*error != '\0') {
+		memprintf(err, "%s: invalid number %s (error at '%c')", args[0], args[1], *error);
+		return -1;
+	}
+	return 0;
+}
+
+
 /* This function is called by the main configuration key "lua-load". It loads and
  * execute an lua file during the parsing of the HAProxy configuration file. It is
  * the main lua entry point.
@@ -4476,6 +4505,7 @@
 	{ CFG_GLOBAL, "tune.lua.session-timeout", hlua_session_timeout },
 	{ CFG_GLOBAL, "tune.lua.task-timeout",    hlua_task_timeout },
 	{ CFG_GLOBAL, "tune.lua.forced-yield",    hlua_forced_yield },
+	{ CFG_GLOBAL, "tune.lua.maxmem",          hlua_parse_maxmem },
 	{ 0, NULL, NULL },
 }};
 
@@ -4528,6 +4558,44 @@
 	return 1;
 }
 
+/* The memory allocator used by the Lua stack. <ud> is a pointer to the
+ * allocator's context. <ptr> is the pointer to alloc/free/realloc. <osize>
+ * is the previously allocated size or the kind of object in case of a new
+ * allocation. <nsize> is the requested new size.
+ */
+static void *hlua_alloc(void *ud, void *ptr, size_t osize, size_t nsize)
+{
+	struct hlua_mem_allocator *zone = ud;
+
+	if (nsize == 0) {
+		/* it's a free */
+		if (ptr)
+			zone->allocated -= osize;
+		free(ptr);
+		return NULL;
+	}
+
+	if (!ptr) {
+		/* it's a new allocation */
+		if (zone->limit && zone->allocated + nsize > zone->limit)
+			return NULL;
+
+		ptr = malloc(nsize);
+		if (ptr)
+			zone->allocated += nsize;
+		return ptr;
+	}
+
+	/* it's a realloc */
+	if (zone->limit && zone->allocated + nsize - osize > zone->limit)
+		return NULL;
+
+	ptr = realloc(ptr, nsize);
+	if (ptr)
+		zone->allocated += nsize - osize;
+	return ptr;
+}
+
 void hlua_init(void)
 {
 	int i;
@@ -4572,6 +4640,9 @@
 	gL.Tref = LUA_REFNIL;
 	gL.task = NULL;
 
+	/* change the memory allocators to track memory usage */
+	lua_setallocf(gL.T, hlua_alloc, &hlua_global_allocator);
+
 	/* Initialise lua. */
 	luaL_openlibs(gL.T);