[MEDIUM] introduce the "stats" keyword in global section

Removed old unused MODE_LOG and MODE_STATS, and replaced the "stats"
keyword in the global section. The new "stats" keyword in the global
section is used to create a UNIX socket on which the statistics will
be accessed.  The client must issue a "show stat\n" command in order
to get a CSV-formated output similar to the output on the HTTP socket
in CSV mode.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 97c427c..f72a95b 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -4,7 +4,7 @@
                          ----------------------
                              version 1.3.13
                              willy tarreau
-                               2007/10/15
+                               2007/10/18
 
 
 This document covers the configuration language as implemented in the version
@@ -39,6 +39,7 @@
    - uid
    - ulimit-n
    - user
+   - stats
   
  * Performance tuning
    - maxconn
@@ -52,7 +53,6 @@
  * Debugging
    - debug
    - quiet
-   - stats
 
 
 1.1) Process management and security
@@ -111,6 +111,28 @@
   the "-p" command line argument. The file must be accessible to the user
   starting the process. See also "daemon".
 
+stats socket <path> [{uid | user} <uid>] [{gid | group} <gid>] [mode <mode>]
+  Creates a UNIX socket in stream mode at location <path>. Any previously
+  existing socket will be backed up then replaced. Connections to this socket
+  will get a CSV-formated output of the process statistics in response to the
+  "show stat" command followed by a line feed. On platforms which support it,
+  it is possible to restrict access to this socket by specifying numerical IDs
+  after "uid" and "gid", or valid user and group names after the "user" and
+  "group" keywords. It is also possible to restrict permissions on the socket
+  by passing an octal value after the "mode" keyword (same syntax as chmod).
+  Depending on the platform, the permissions on the socket will be inherited
+  from the directory which hosts it, or from the user the process is started
+  with.
+
+stats timeout <timeout, in milliseconds>
+  The default timeout on the stats socket is set to 10 seconds. It is possible
+  to change this value with "stats timeout". The value must be passed in
+  milliseconds.
+
+stats maxconn <connections>
+  By default, the stats socket is limited to 10 concurrent connections. It is
+  possible to change this value with "stats maxconn".
+
 uid <number>
   Changes the process' user ID to <number>. It is recommended that the user ID
   is dedicated to HAProxy or to a small set of similar daemons. HAProxy must
@@ -186,10 +208,6 @@
   Do not display any message during startup. It is equivalent to the command-
   line argument "-q".
 
-stats
-  Dump internal statistics to stdout at regular interval. It is available for
-  development purposes only and should never be set.
-
 
 2) Proxies
 ----------
diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h
index bcf1540..efe5fa9 100644
--- a/include/proto/dumpstats.h
+++ b/include/proto/dumpstats.h
@@ -29,6 +29,7 @@
 
 #define STAT_FMT_HTML 0x1
 
+int stats_parse_global(const char **args, char *err, int errlen);
 int stats_dump_raw(struct session *s, struct uri_auth *uri, int flags);
 int stats_dump_http(struct session *s, struct uri_auth *uri, int flags);
 int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri, int flags);
diff --git a/include/types/global.h b/include/types/global.h
index bf95ffd..bed62d8 100644
--- a/include/types/global.h
+++ b/include/types/global.h
@@ -29,15 +29,13 @@
 #include <types/task.h>
 
 /* modes of operation (global.mode) */
-#define	MODE_DEBUG	1
-#define	MODE_STATS	2
-#define	MODE_LOG	4
-#define	MODE_DAEMON	8
-#define	MODE_QUIET	16
-#define	MODE_CHECK	32
-#define	MODE_VERBOSE	64
-#define	MODE_STARTING	128
-#define	MODE_FOREGROUND	256
+#define	MODE_DEBUG	0x01
+#define	MODE_DAEMON	0x02
+#define	MODE_QUIET	0x04
+#define	MODE_CHECK	0x08
+#define	MODE_VERBOSE	0x10
+#define	MODE_STARTING	0x20
+#define	MODE_FOREGROUND	0x40
 
 /* list of last checks to perform, depending on config options */
 #define LSTCHK_CAP_BIND	0x00000001	/* check that we can bind to any port */
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 53c900a..d9e01a7 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -41,6 +41,7 @@
 #include <proto/backend.h>
 #include <proto/buffers.h>
 #include <proto/checks.h>
+#include <proto/dumpstats.h>
 #include <proto/httperr.h>
 #include <proto/log.h>
 #include <proto/proxy.h>
@@ -279,7 +280,11 @@
 		global.mode |= MODE_QUIET;
 	}
 	else if (!strcmp(args[0], "stats")) {
-		global.mode |= MODE_STATS;
+		memcpy(trash, "error near 'stats'", 19);
+		if (stats_parse_global((const char **)args + 1, trash, sizeof(trash)) < 0) {
+			Alert("parsing [%s:%d] : %s\n", file, linenum, trash);
+			return -1;
+		}
 	}
 	else if (!strcmp(args[0], "tune.maxpollevents")) {
 		if (global.tune.maxpollevents != 0) {
diff --git a/src/dumpstats.c b/src/dumpstats.c
index 3fdac86..bba8c2c 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -17,6 +17,8 @@
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
+#include <pwd.h>
+#include <grp.h>
 
 #include <sys/socket.h>
 #include <sys/stat.h>
@@ -42,9 +44,114 @@
 #include <proto/buffers.h>
 #include <proto/dumpstats.h>
 #include <proto/fd.h>
+#include <proto/proto_uxst.h>
 #include <proto/senddata.h>
 #include <proto/session.h>
 
+/* This function parses a "stats" statement in the "global" section. It returns
+ * -1 if there is any error, otherwise zero. If it returns -1, it may write an
+ * error message into ther <err> buffer, for at most <errlen> bytes, trailing
+ * zero included. The trailing '\n' must not be written. The function must be
+ * called with <args> pointing to the first word after "stats".
+ */
+int stats_parse_global(const char **args, char *err, int errlen)
+{
+	if (!strcmp(args[0], "socket")) {
+		struct sockaddr_un su;
+		int cur_arg;
+
+		if (*args[1] == 0) {
+			snprintf(err, errlen, "'stats socket' in global section expects a path to a UNIX socket");
+			return -1;
+		}
+
+		if (global.stats_sock.state != LI_NEW) {
+			snprintf(err, errlen, "'stats socket' already specified in global section");
+			return -1;
+		}
+
+		su.sun_family = AF_UNIX;
+		strncpy(su.sun_path, args[1], sizeof(su.sun_path));
+		su.sun_path[sizeof(su.sun_path) - 1] = 0;
+		memcpy(&global.stats_sock.addr, &su, sizeof(su)); // guaranteed to fit
+
+		global.stats_sock.state = LI_INIT;
+		global.stats_sock.accept = uxst_event_accept;
+		global.stats_sock.handler = process_uxst_stats;
+		global.stats_sock.private = NULL;
+
+		cur_arg = 2;
+		while (*args[cur_arg]) {
+			if (!strcmp(args[cur_arg], "uid")) {
+				global.stats_sock.perm.ux.uid = atol(args[cur_arg + 1]);
+				cur_arg += 2;
+			}
+			else if (!strcmp(args[cur_arg], "gid")) {
+				global.stats_sock.perm.ux.gid = atol(args[cur_arg + 1]);
+				cur_arg += 2;
+			}
+			else if (!strcmp(args[cur_arg], "mode")) {
+				global.stats_sock.perm.ux.mode = strtol(args[cur_arg + 1], NULL, 8);
+				cur_arg += 2;
+			}
+			else if (!strcmp(args[cur_arg], "user")) {
+				struct passwd *user;
+				user = getpwnam(args[cur_arg + 1]);
+				if (!user) {
+					snprintf(err, errlen, "unknown user '%s' in 'global' section ('stats user')",
+						 args[cur_arg + 1]);
+					return -1;
+				}
+				global.stats_sock.perm.ux.uid = user->pw_uid;
+				cur_arg += 2;
+			}
+			else if (!strcmp(args[cur_arg], "group")) {
+				struct group *group;
+				group = getgrnam(args[cur_arg + 1]);
+				if (!group) {
+					snprintf(err, errlen, "unknown group '%s' in 'global' section ('stats group')",
+						 args[cur_arg + 1]);
+					return -1;
+				}
+				global.stats_sock.perm.ux.gid = group->gr_gid;
+				cur_arg += 2;
+			}
+			else {
+				snprintf(err, errlen, "'stats socket' only supports 'user', 'uid', 'group', 'gid', and 'mode'");
+				return -1;
+			}
+		}
+			
+		uxst_add_listener(&global.stats_sock);
+		global.maxsock++;
+	}
+	else if (!strcmp(args[0], "timeout")) {
+		int timeout = atol(args[1]);
+
+		if (timeout <= 0) {
+			snprintf(err, errlen, "a positive value is expected for 'stats timeout' in 'global section'");
+			return -1;
+		}
+		__tv_from_ms(&global.stats_timeout, timeout);
+	}
+	else if (!strcmp(args[0], "maxconn")) {
+		int maxconn = atol(args[1]);
+
+		if (maxconn <= 0) {
+			snprintf(err, errlen, "a positive value is expected for 'stats maxconn' in 'global section'");
+			return -1;
+		}
+		global.maxsock -= global.stats_sock.maxconn;
+		global.stats_sock.maxconn = maxconn;
+		global.maxsock += global.stats_sock.maxconn;
+	}
+	else {
+		snprintf(err, errlen, "'stats' only supports 'socket', 'maxconn' and 'timeout' in 'global' section");
+		return -1;
+	}
+	return 0;
+}
+
 /*
  * Produces statistics data for the session <s>. Expects to be called with
  * s->cli_state == CL_STSHUTR. It *may* make use of informations from <uri>
diff --git a/src/haproxy.c b/src/haproxy.c
index c13c392..c57e23f 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -116,6 +116,12 @@
 	logfac2 : -1,
 	loglev1 : 7, /* max syslog level : debug */
 	loglev2 : 7,
+	.stats_timeout = { .tv_sec = 10, .tv_usec = 0 }, /* stats timeout = 10 seconds */
+	.stats_sock.timeout = &global.stats_timeout,
+	.stats_sock.maxconn = 10, /* 10 concurrent stats connections */
+	.stats_sock.perm.ux.uid = -1,
+	.stats_sock.perm.ux.gid = -1,
+	.stats_sock.perm.ux.mode = 0,
 	/* others NULL OK */
 };
 
@@ -542,7 +548,7 @@
 		global.mode &= ~(MODE_DAEMON | MODE_QUIET);
 	}
 	global.mode |= (arg_mode & (MODE_DAEMON | MODE_FOREGROUND | MODE_QUIET |
-				    MODE_VERBOSE | MODE_DEBUG | MODE_STATS | MODE_LOG));
+				    MODE_VERBOSE | MODE_DEBUG ));
 
 	if ((global.mode & MODE_DEBUG) && (global.mode & (MODE_DAEMON | MODE_QUIET))) {
 		Warning("<debug> mode incompatible with <quiet> and <daemon>. Keeping <debug> only.\n");