MEDIUM: lua: Add cli handler for Lua

Now, HAProxy allows to register some keys in the "cli". This patch allows
to handle these keys with Lua code.
diff --git a/doc/lua-api/index.rst b/doc/lua-api/index.rst
index 32ef143..530cd59 100644
--- a/doc/lua-api/index.rst
+++ b/doc/lua-api/index.rst
@@ -519,6 +519,47 @@
 
   It takes no input, and no output is expected.
 
+.. js:function:: core.register_cli([path], usage, func)
+
+  **context**: body
+
+  Register and start independent task. The task is started when the HAProxy
+  main scheduler starts. For example this type of tasks can be executed to
+  perform complex health checks.
+
+  :param array path: is the sequence of word for which the cli execute the Lua
+    binding.
+  :param string usage: is the usage message displayed in the help.
+  :param function func: is the Lua function called to handle the CLI commands.
+
+  The prototype of the Lua function used as argument is:
+
+.. code-block:: lua
+
+    function(AppletTCP, [arg1, [arg2, [...]]])
+..
+
+  I/O are managed with the :ref:`applettcp_class` object. Args are given as
+  paramter. The args embbed the registred path. If the path is declared like
+  this:
+
+.. code-block:: lua
+
+    core.register_cli({"show", "ssl", "stats"}, "Display SSL stats..", function(applet, arg1, arg2, arg3, arg4, arg5)
+	 end)
+..
+
+  And we execute this in the prompt:
+
+.. code-block:: text
+
+    > prompt
+    > show ssl stats all
+..
+
+  Then, arg1, arg2 and arg3 will contains respectivey "show", "ssl" and "stats".
+  arg4 will contain "all". arg5 contains nil.
+
 .. js:function:: core.set_nice(nice)
 
   **context**: task, action, sample-fetch, converter
diff --git a/include/types/applet.h b/include/types/applet.h
index e60b08a..cffc832 100644
--- a/include/types/applet.h
+++ b/include/types/applet.h
@@ -123,6 +123,10 @@
 		} hlua;
 		struct {
 			struct hlua hlua;
+			struct task *task;
+		} hlua_cli;
+		struct {
+			struct hlua hlua;
 			int flags;
 			struct task *task;
 		} hlua_apptcp;
diff --git a/src/hlua.c b/src/hlua.c
index 7e56b75..02e9b94 100644
--- a/src/hlua.c
+++ b/src/hlua.c
@@ -32,6 +32,7 @@
 #include <proto/applet.h>
 #include <proto/channel.h>
 #include <proto/connection.h>
+#include <proto/dumpstats.h>
 #include <proto/hdr_idx.h>
 #include <proto/hlua.h>
 #include <proto/hlua_fcn.h>
@@ -6383,6 +6384,239 @@
 	return 0;
 }
 
+/* This function initialises Lua cli handler. It copies the
+ * arguments in the Lua stack and create channel IO objects.
+ */
+static int hlua_cli_parse_fct(char **args, struct appctx *appctx, void *private)
+{
+	struct hlua *hlua;
+	struct hlua_function *fcn;
+	int i;
+	const char *error;
+
+	hlua = &appctx->ctx.hlua_cli.hlua;
+	fcn = private;
+	appctx->private = private;
+
+	/* Create task used by signal to wakeup applets.
+	 * We use the same wakeup fonction than the Lua applet_tcp and
+	 * applet_http. It is absolutely compatible.
+	 */
+	appctx->ctx.hlua_cli.task = task_new();
+	if (!appctx->ctx.hlua_cli.task) {
+		SEND_ERR(NULL, "Lua applet tcp '%s': out of memory.\n", fcn->name);
+		return 0;
+	}
+	appctx->ctx.hlua_cli.task->nice = 0;
+	appctx->ctx.hlua_cli.task->context = appctx;
+	appctx->ctx.hlua_cli.task->process = hlua_applet_wakeup;
+
+	/* Initialises the Lua context */
+	if (!hlua_ctx_init(hlua, appctx->ctx.hlua_cli.task)) {
+		SEND_ERR(NULL, "Lua cli '%s': can't initialize Lua context.\n", fcn->name);
+		return 1;
+	}
+
+	/* The following Lua calls can fail. */
+	if (!SET_SAFE_LJMP(hlua->T)) {
+		if (lua_type(hlua->T, -1) == LUA_TSTRING)
+			error = lua_tostring(hlua->T, -1);
+		else
+			error = "critical error";
+		SEND_ERR(NULL, "Lua cli '%s': %s.\n", fcn->name, error);
+		goto error;
+	}
+
+	/* Check stack available size. */
+	if (!lua_checkstack(hlua->T, 2)) {
+		SEND_ERR(NULL, "Lua cli '%s': full stack.\n", fcn->name);
+		goto error;
+	}
+
+	/* Restore the function in the stack. */
+	lua_rawgeti(hlua->T, LUA_REGISTRYINDEX, fcn->function_ref);
+
+	/* Once the arguments parsed, the CLI is like an AppletTCP,
+	 * so push AppletTCP in the stack.
+	 * TODO: get_priv() and set_priv() are useless. Maybe we will
+	 * create a new object without these two functions.
+	 */
+	if (!hlua_applet_tcp_new(hlua->T, appctx)) {
+		SEND_ERR(NULL, "Lua cli '%s': full stack.\n", fcn->name);
+		goto error;
+	}
+	hlua->nargs = 1;
+
+	/* push keywords in the stack. */
+	for (i = 0; *args[i]; i++) {
+		/* Check stack available size. */
+		if (!lua_checkstack(hlua->T, 1)) {
+			SEND_ERR(NULL, "Lua cli '%s': full stack.\n", fcn->name);
+			goto error;
+		}
+		lua_pushstring(hlua->T, args[i]);
+		hlua->nargs++;
+	}
+
+	/* We must initialize the execution timeouts. */
+	hlua->max_time = hlua_timeout_session;
+
+	/* At this point the execution is safe. */
+	RESET_SAFE_LJMP(hlua->T);
+
+	/* It's ok */
+	return 0;
+
+	/* It's not ok. */
+error:
+	RESET_SAFE_LJMP(hlua->T);
+	hlua_ctx_destroy(hlua);
+	return 1;
+}
+
+static int hlua_cli_io_handler_fct(struct appctx *appctx)
+{
+	struct hlua *hlua;
+	struct stream_interface *si;
+	struct hlua_function *fcn;
+
+	hlua = &appctx->ctx.hlua_cli.hlua;
+	si = appctx->owner;
+	fcn = appctx->private;
+
+	/* If the stream is disconnect or closed, ldo nothing. */
+	if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO))
+		return 1;
+
+	/* Execute the function. */
+	switch (hlua_ctx_resume(hlua, 1)) {
+
+	/* finished. */
+	case HLUA_E_OK:
+		return 1;
+
+	/* yield. */
+	case HLUA_E_AGAIN:
+		/* We want write. */
+		if (HLUA_IS_WAKERESWR(hlua))
+			si_applet_cant_put(si);
+		/* Set the timeout. */
+		if (hlua->wake_time != TICK_ETERNITY)
+			task_schedule(hlua->task, hlua->wake_time);
+		return 0;
+
+	/* finished with error. */
+	case HLUA_E_ERRMSG:
+		/* Display log. */
+		SEND_ERR(NULL, "Lua cli '%s': %s.\n",
+		         fcn->name, lua_tostring(hlua->T, -1));
+		lua_pop(hlua->T, 1);
+		return 1;
+
+	case HLUA_E_ERR:
+		/* Display log. */
+		SEND_ERR(NULL, "Lua cli '%s' return an unknown error.\n",
+		         fcn->name);
+		return 1;
+
+	default:
+		return 1;
+	}
+
+	return 1;
+}
+
+static void hlua_cli_io_release_fct(struct appctx *appctx)
+{
+	struct hlua *hlua;
+
+	hlua = &appctx->ctx.hlua_cli.hlua;
+	hlua_ctx_destroy(hlua);
+}
+
+/* This function is an LUA binding used for registering
+ * new keywords in the cli. It expects a list of keywords
+ * which are the "path". It is limited to 5 keywords. A
+ * description of the command, a function to be executed
+ * for the parsing and a function for io handlers.
+ */
+__LJMP static int hlua_register_cli(lua_State *L)
+{
+	struct cli_kw_list *cli_kws;
+	const char *message;
+	int ref_io;
+	int len;
+	struct hlua_function *fcn;
+	int index;
+	int i;
+
+	MAY_LJMP(check_args(L, 3, "register_cli"));
+
+	/* First argument : an array of maximum 5 keywords. */
+	if (!lua_istable(L, 1))
+		WILL_LJMP(luaL_argerror(L, 1, "1st argument must be a table"));
+
+	/* Second argument : string with contextual message. */
+	message = MAY_LJMP(luaL_checkstring(L, 2));
+
+	/* Third and fourth argument : lua function. */
+	ref_io = MAY_LJMP(hlua_checkfunction(L, 3));
+
+	/* Allocate and fill the sample fetch keyword struct. */
+	cli_kws = calloc(1, sizeof(*cli_kws) + sizeof(struct cli_kw) * 2);
+	if (!cli_kws)
+		WILL_LJMP(luaL_error(L, "lua out of memory error."));
+	fcn = calloc(1, sizeof(*fcn));
+	if (!fcn)
+		WILL_LJMP(luaL_error(L, "lua out of memory error."));
+
+	/* Fill path. */
+	index = 0;
+	lua_pushnil(L);
+	while(lua_next(L, 1) != 0) {
+		if (index >= 5)
+			WILL_LJMP(luaL_argerror(L, 1, "1st argument must be a table with a maximum of 5 entries"));
+		if (lua_type(L, -1) != LUA_TSTRING)
+			WILL_LJMP(luaL_argerror(L, 1, "1st argument must be a table filled with strings"));
+		cli_kws->kw[0].str_kw[index] = strdup(lua_tostring(L, -1));
+		if (!cli_kws->kw[0].str_kw[index])
+			WILL_LJMP(luaL_error(L, "lua out of memory error."));
+		index++;
+		lua_pop(L, 1);
+	}
+
+	/* Copy help message. */
+	cli_kws->kw[0].usage = strdup(message);
+	if (!cli_kws->kw[0].usage)
+		WILL_LJMP(luaL_error(L, "lua out of memory error."));
+
+	/* Fill fcn io handler. */
+	len = strlen("<lua.cli>") + 1;
+	for (i = 0; i < index; i++)
+		len += strlen(cli_kws->kw[0].str_kw[i]) + 1;
+	fcn->name = calloc(1, len);
+	if (!fcn->name)
+		WILL_LJMP(luaL_error(L, "lua out of memory error."));
+	strncat((char *)fcn->name, "<lua.cli", len);
+	for (i = 0; i < index; i++) {
+		strncat((char *)fcn->name, ".", len);
+		strncat((char *)fcn->name, cli_kws->kw[0].str_kw[i], len);
+	}
+	strncat((char *)fcn->name, ">", len);
+	fcn->function_ref = ref_io;
+
+	/* Fill last entries. */
+	cli_kws->kw[0].private = fcn;
+	cli_kws->kw[0].parse = hlua_cli_parse_fct;
+	cli_kws->kw[0].io_handler = hlua_cli_io_handler_fct;
+	cli_kws->kw[0].io_release = hlua_cli_io_release_fct;
+
+	/* Register this new converter */
+	cli_register_kw(cli_kws);
+
+	return 0;
+}
+
 static int hlua_read_timeout(char **args, int section_type, struct proxy *curpx,
                              struct proxy *defpx, const char *file, int line,
                              char **err, unsigned int *timeout)
@@ -6686,6 +6920,7 @@
 	hlua_class_function(gL.T, "register_converters", hlua_register_converters);
 	hlua_class_function(gL.T, "register_action", hlua_register_action);
 	hlua_class_function(gL.T, "register_service", hlua_register_service);
+	hlua_class_function(gL.T, "register_cli", hlua_register_cli);
 	hlua_class_function(gL.T, "yield", hlua_yield);
 	hlua_class_function(gL.T, "set_nice", hlua_set_nice);
 	hlua_class_function(gL.T, "sleep", hlua_sleep);