[MINOR] add "description", "node" and show-node"/"show-desc", remove "node-name", v2

This patch implements "description" (proxy and global) and "node" (global)
options, removes "node-name" and adds "show-node" & "show-desc" options
for "stats". It also changes the way the header lines (with proxy name) and
the statistics are displayed, so stats no longer look so clumsy with very
long names.

Instead of "node-name" it is possible to use show-node/show-desc with
an optional parameter that overrides a default node/description.

backend cust-0045
        # report specific values for this customer
        stats show-node Europe
        stats show-desc Master node for Europe, Asia, Africa
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 2055a09..c7e582d 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -383,6 +383,8 @@
    - ulimit-n
    - user
    - stats
+   - node
+   - description
   
  * Performance tuning
    - maxconn
@@ -514,6 +516,21 @@
   Similar to "uid" but uses the UID of user name <user name> from /etc/passwd.
   See also "uid" and "group".
 
+node <name>
+  Only letters, digits, hyphen and underscore are allowed, like in DNS names.
+
+  This statement is useful in HA configurations where two or more processes or
+  servers share the same IP address. By setting a different node-name on all
+  nodes, it becomes easy to immediately spot what server is handling the
+  traffic.
+
+description <text>
+  Add a text that describes the instance.
+
+  Please note that it is required to escape certain characters (# for example)
+  and this text is inserted into a html page so you should avoid using
+  "<" and ">" characters.
+
 
 3.2. Performance tuning
 -----------------------
@@ -696,6 +713,7 @@
 contimeout                  X          -         X         X  (deprecated)
 cookie                      X          -         X         X
 default_backend             -          X         X         -
+description                 -          X         X         X
 disabled                    X          X         X         X
 dispatch                    -          -         X         X
 enabled                     X          X         X         X
@@ -3785,22 +3803,43 @@
   See also : "stats auth", "stats realm", "stats uri"
 
 
-stats node-name [ <name> ]
+stats show-node [ <name> ]
   Enable reporting of a host name on the statistics page.
   May be used in sections :   defaults | frontend | listen | backend
                                  yes   |    no    |   yes  |   yes
-  Arguments :
-    <name>    is an optional name to be reported. If unspecified, the system's
-              hostname is automatically used instead.
+  Arguments:
+    <name>    is an optional name to be reported. If unspecified, the
+              node name from global section is automatically used instead.
 
-  The node-name is read as a single word, so any spaces in it should be escaped
-  using a backslash ('\'). If it is left unspecified, the system's hostname is
-  used instead.
+  This statement is useful for users that offer shared services to their
+  customers, where node or description might be different on a stats page
+  provided for each customer.
 
-  This statement is useful in HA configurations where two or more processes or
-  servers share a same IP address. By setting a different node-name on all
-  nodes, it becomes easy to immediately spot what server is handling the
-  traffic.
+  Though this statement alone is enough to enable statistics reporting, it is
+  recommended to set all other settings in order to avoid relying on default
+  unobvious parameters.
+
+  Example:
+    # internal monitoring access (unlimited)
+    backend private_monitoring
+        stats enable
+        stats show-node Europe-1
+        stats uri       /admin?stats
+        stats refresh   5s
+
+  See also: "show-desc", "stats enable", "stats uri", and "node" in global section.
+
+
+stats show-desc [ <description> ]
+  Enable reporting of a description on the statistics page.
+  May be used in sections :   defaults | frontend | listen | backend
+                                 yes   |    no    |   yes  |   yes
+
+    <name>    is an optional description to be reported. If unspecified, the
+              description from global section is automatically used instead.
+
+  This statement is useful for users that offer shared services to their
+  customers, where node or description should be different for each customer.
 
   Though this statement alone is enough to enable statistics reporting, it is
   recommended to set all other settings in order to avoid relying on default
@@ -3810,11 +3849,11 @@
     # internal monitoring access (unlimited)
     backend private_monitoring
         stats enable
-        stats node-name master
+        stats show-desc Master node for Europe, Asia, Africa
         stats uri       /admin?stats
         stats refresh   5s
 
-  See also : "stats enable", "stats uri"
+  See also: "show-node", "stats enable", "stats uri" and "description" in global section.
 
 
 stats realm <realm>
diff --git a/include/common/uri_auth.h b/include/common/uri_auth.h
index 132be21..3a7fd9c 100644
--- a/include/common/uri_auth.h
+++ b/include/common/uri_auth.h
@@ -32,13 +32,15 @@
 };
 
 #define	ST_HIDEVER	0x00000001	/* do not report the version and reldate */
+#define	ST_SHNODE	0x00000002	/* show node name */
+#define	ST_SHDESC	0x00000004	/* show description */
 
 /* later we may link them to support multiple URI matching */
 struct uri_auth {
 	int uri_len;			/* the prefix length */
 	char *uri_prefix;		/* the prefix we want to match */
 	char *auth_realm;		/* the realm reported to the client */
-	char *node_name;		/* the node name reported to the client */
+	char *node, *desc;		/* node name & description reported in this stats */
 	int refresh;			/* refresh interval for the browser (in seconds) */
 	int flags;			/* some flags describing the statistics page */
 	struct user_auth *users;	/* linked list of valid user:passwd couples */
@@ -75,7 +77,8 @@
 struct uri_auth *stats_set_flag(struct uri_auth **root, int flag);
 struct uri_auth *stats_add_auth(struct uri_auth **root, char *user);
 struct uri_auth *stats_add_scope(struct uri_auth **root, char *scope);
-struct uri_auth *stats_set_node_name(struct uri_auth **root, char *name);
+struct uri_auth *stats_set_node(struct uri_auth **root, char *name);
+struct uri_auth *stats_set_desc(struct uri_auth **root, char *desc);
 
 #endif /* _COMMON_URI_AUTH_H */
 
diff --git a/include/types/global.h b/include/types/global.h
index 4d349c3..3a8faa9 100644
--- a/include/types/global.h
+++ b/include/types/global.h
@@ -70,6 +70,7 @@
 	int spread_checks;
 	char *chroot;
 	char *pidfile;
+	char *node, *desc;		/* node name & description */
 	int logfac1, logfac2;
 	int loglev1, loglev2;
 	int minlvl1, minlvl2;
diff --git a/include/types/proxy.h b/include/types/proxy.h
index 8c93e55..2cdf689 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -233,7 +233,7 @@
 		int httpreq;                    /* maximum time for complete HTTP request */
 		int check;                      /* maximum time for complete check */
 	} timeout;
-	char *id;				/* proxy id */
+	char *id, *desc;			/* proxy id (name) and description */
 	struct list pendconns;			/* pending connections with no server assigned yet */
 	int nbpend, nbpend_max;			/* number of pending connections with no server assigned yet */
 	int totpend;				/* total number of pending connections on this instance (for stats) */
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 7938654..3e74634 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -603,6 +603,52 @@
 		}
 		global.chroot = strdup(args[1]);
 	}
+	else if (!strcmp(args[0], "description")) {
+		int i, len=0;
+		char *d;
+
+		if (!*args[1]) {
+			Alert("parsing [%s:%d]: '%s' expects a string argument.\n",
+				file, linenum, args[0]);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+
+		for(i=1; *args[i]; i++)
+			len += strlen(args[i])+1;
+
+		if (global.desc)
+			free(global.desc);
+
+		global.desc = d = (char *)calloc(1, len);
+
+		d += sprintf(d, "%s", args[1]);
+		for(i=2; *args[i]; i++)
+			d += sprintf(d, " %s", args[i]);
+	}
+	else if (!strcmp(args[0], "node")) {
+		int i;
+		char c;
+
+		for (i=0; args[1][i]; i++) {
+			c = args[1][i];
+			if (!isupper(c) && !islower(c) && !isdigit(c) && c != '_' && c != '-' && c != '.')
+				break;
+		}
+
+		if (!i || args[1][i]) {
+			Alert("parsing [%s:%d]: '%s' requires valid node name - non-empty string"
+				" with digits(0-9), letters(A-Z, a-z), dot(.), hyphen(-) or underscode(_).\n",
+				file, linenum, args[0]);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+
+		if (global.node)
+			free(global.node);
+
+		global.node = strdup(args[1]);
+	}
 	else if (!strcmp(args[0], "pidfile")) {
 		if (global.pidfile != NULL) {
 			Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]);
@@ -728,6 +774,7 @@
 		Alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section\n", file, linenum, args[0], "global");
 		err_code |= ERR_ALERT | ERR_FATAL;
 	}
+
  out:
 	return err_code;
 }
@@ -1179,6 +1226,27 @@
 				goto out;
 			}
 	}
+	else if (!strcmp(args[0], "description")) {
+		int i, len=0;
+		char *d;
+
+		if (!*args[1]) {
+			Alert("parsing [%s:%d]: '%s' expects a string argument.\n",
+				file, linenum, args[0]);
+			return -1;
+		}
+
+		for(i=1; *args[i]; i++)
+			len += strlen(args[i])+1;
+
+		d = (char *)calloc(1, len);
+		curproxy->desc = d;
+
+		d += sprintf(d, "%s", args[1]);
+		for(i=2; *args[i]; i++)
+			d += sprintf(d, " %s", args[i]);
+
+	}
 	else if (!strcmp(args[0], "disabled")) {  /* disables this proxy */
 		curproxy->state = PR_STSTOPPED;
 	}
@@ -1744,7 +1812,7 @@
 			curproxy->uri_auth = NULL; /* we must detach from the default config */
 
 		if (*(args[1]) == 0) {
-			Alert("parsing [%s:%d] : '%s' expects 'uri', 'realm', 'node-name', 'auth', 'scope' or 'enable'.\n", file, linenum, args[0]);
+			Alert("parsing [%s:%d] : '%s' expects 'uri', 'realm', 'auth', 'scope' or 'enable', 'hide-version', 'show-node', 'show-desc'.\n", file, linenum, args[0]);
 			err_code |= ERR_ALERT | ERR_FATAL;
 			goto out;
 		} else if (!strcmp(args[1], "uri")) {
@@ -1767,12 +1835,6 @@
 				err_code |= ERR_ALERT | ERR_ABORT;
 				goto out;
 			}
-		} else if (!strcmp(args[1], "node-name")) {
-			if (!stats_set_node_name(&curproxy->uri_auth, *(args[2]) ? args[2] : hostname)) {
-				Alert("parsing [%s:%d] : out of memory.\n", file, linenum);
-				err_code |= ERR_ALERT | ERR_ABORT;
-				goto out;
-			}
 		} else if (!strcmp(args[1], "refresh")) {
 			unsigned interval;
 
@@ -1819,6 +1881,61 @@
 				err_code |= ERR_ALERT | ERR_ABORT;
 				goto out;
 			}
+		} else if (!strcmp(args[1], "show-node")) {
+
+			if (*args[2]) {
+				int i;
+				char c;
+
+				for (i=0; args[2][i]; i++) {
+					c = args[2][i];
+					if (!isupper(c) && !islower(c) && !isdigit(c) && c != '_' && c != '-')
+						break;
+				}
+
+				if (!i || args[2][i]) {
+					Alert("parsing [%s:%d]: '%s %s' invalid node name - should be a string"
+						"with digits(0-9), letters(A-Z, a-z), hyphen(-) or underscode(_).\n",
+						file, linenum, args[0], args[1]);
+					err_code |= ERR_ALERT | ERR_FATAL;
+					goto out;
+				}
+			}
+
+			if (!stats_set_node(&curproxy->uri_auth, args[2])) {
+				Alert("parsing [%s:%d]: out of memory.\n", file, linenum);
+				err_code |= ERR_ALERT | ERR_ABORT;
+				goto out;
+			}
+		} else if (!strcmp(args[1], "show-desc")) {
+			char *desc = NULL;
+
+			if (*args[2]) {
+				int i, len=0;
+				char *d;
+
+				for(i=2; *args[i]; i++)
+					len += strlen(args[i])+1;
+
+				desc = d = (char *)calloc(1, len);
+
+				d += sprintf(d, "%s", args[2]);
+				for(i=3; *args[i]; i++)
+					d += sprintf(d, " %s", args[i]);
+			}
+
+			if (!*args[2] && !global.desc)
+				Warning("parsing [%s:%d]: '%s' requires a parameter or 'desc' to be set in the global section.\n",
+					file, linenum, args[1]);
+			else {
+				if (!stats_set_desc(&curproxy->uri_auth, desc)) {
+					free(desc);
+					Alert("parsing [%s:%d]: out of memory.\n", file, linenum);
+					err_code |= ERR_ALERT | ERR_ABORT;
+					goto out;
+				}
+				free(desc);
+			}
 		} else {
 			Alert("parsing [%s:%d] : unknown stats parameter '%s' (expects 'hide-version', 'uri', 'realm', 'auth' or 'enable').\n",
 			      file, linenum, args[0]);
diff --git a/src/dumpstats.c b/src/dumpstats.c
index 86ce23e..7199fe0 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -574,6 +574,8 @@
 				     "PipesFree: %d\n"
 				     "Tasks: %d\n"
 				     "Run_queue: %d\n"
+				     "node: %s\n"
+				     "description: %s\n"
 				     "",
 				     global.nbproc,
 				     relative_pid,
@@ -584,7 +586,8 @@
 				     global.rlimit_nofile,
 				     global.maxsock, global.maxconn, global.maxpipes,
 				     actconn, pipes_used, pipes_free,
-				     nb_tasks_cur, run_queue_cur
+				     nb_tasks_cur, run_queue_cur,
+				     global.node, global.desc?global.desc:""
 				     );
 			if (buffer_write_chunk(rep, &msg) >= 0)
 				return 0;
@@ -752,8 +755,7 @@
 			     " border-color: black;"
 			     " border-bottom-style: solid;"
 			     "}\n"
-			     ".pxname	{background: #b00040;color: #ffff40;font-weight: bold;}\n"
-			     ".titre	{background: #20D0D0;color: #000000;font-weight: bold;}\n"
+			     ".titre	{background: #20D0D0;color: #000000; font-weight: bold;}\n"
 			     ".total	{background: #20D0D0;color: #ffff80;}\n"
 			     ".frontend	{background: #e8e8d0;}\n"
 			     ".backend	{background: #e8e8d0;}\n"
@@ -775,14 +777,16 @@
 			     "table.tbl { border-collapse: collapse; border-style: none;}\n"
 			     "table.tbl td { border-width: 1px 1px 1px 1px; border-style: solid solid solid solid; padding: 2px 3px; border-color: gray;}\n"
 			     "table.tbl th { border-width: 1px; border-style: solid solid solid solid; border-color: gray;}\n"
+			     "table.tbl th.pxname {background: #b00040; color: #ffff40; font-weight: bold; border-style: solid solid none solid; padding: 2px 3px;}\n"
 			     "table.tbl th.empty { border-style: none; empty-cells: hide; background: white;}\n"
+			     "table.tbl th.desc { background: white; border-style: solid solid none solid; text-align: left; padding: 2px 3px;}\n"
 			     "table.lgd { border-collapse: collapse; border-width: 1px; border-style: none none none solid; border-color: black;}\n"
 			     "table.lgd td { border-width: 1px; border-style: solid solid solid solid; border-color: gray; padding: 2px;}\n"
 			     "table.lgd td.noborder { border-style: none; padding: 2px; white-space: nowrap;}\n"
 			     "-->\n"
 			     "</style></head>\n",
-			     uri->node_name ? " on " : "",
-			     uri->node_name ? uri->node_name : ""
+			     (uri->flags&ST_SHNODE) ? " on " : "",
+			     (uri->flags&ST_SHNODE) ? (uri->node ? uri->node : global.node) : ""
 			     );
 		} else {
 			print_csv_header(&msg);
@@ -804,16 +808,16 @@
 			chunk_printf(&msg,
 			     "<body><h1><a href=\"" PRODUCT_URL "\" style=\"text-decoration: none;\">"
 			     PRODUCT_NAME "%s</a></h1>\n"
-			     "<h2>Statistics Report for pid %d%s%s</h2>\n"
+			     "<h2>Statistics Report for pid %d%s%s%s%s</h2>\n"
 			     "<hr width=\"100%%\" class=\"hr\">\n"
 			     "<h3>&gt; General process information</h3>\n"
 			     "<table border=0 cols=4><tr><td align=\"left\" nowrap width=\"1%%\">\n"
 			     "<p><b>pid = </b> %d (process #%d, nbproc = %d)<br>\n"
 			     "<b>uptime = </b> %dd %dh%02dm%02ds<br>\n"
-			     "<b>system limits :</b> memmax = %s%s ; ulimit-n = %d<br>\n"
-			     "<b>maxsock = </b> %d ; <b>maxconn = </b> %d ; <b>maxpipes = </b> %d<br>\n"
-			     "current conns = %d ; current pipes = %d/%d<br>\n"
-			     "Running tasks : %d/%d<br>\n"
+			     "<b>system limits:</b> memmax = %s%s; ulimit-n = %d<br>\n"
+			     "<b>maxsock = </b> %d; <b>maxconn = </b> %d; <b>maxpipes = </b> %d<br>\n"
+			     "current conns = %d; current pipes = %d/%d<br>\n"
+			     "Running tasks: %d/%d<br>\n"
 			     "</td><td align=\"center\" nowrap>\n"
 			     "<table class=\"lgd\"><tr>\n"
 			     "<td class=\"active3\">&nbsp;</td><td class=\"noborder\">active UP </td>"
@@ -834,9 +838,9 @@
 			     "<b>Display option:</b><ul style=\"margin-top: 0.25em;\">"
 			     "",
 			     (uri->flags&ST_HIDEVER)?"":(STATS_VERSION_STRING),
-			     pid, uri->node_name ? " on " : "", uri->node_name ? uri->node_name : "",
-			     pid,
-			     relative_pid, global.nbproc,
+			     pid, (uri->flags&ST_SHNODE) ? " on " : "", (uri->flags&ST_SHNODE) ? (uri->node ? uri->node : global.node) : "",
+			     (uri->flags&ST_SHDESC)? ": " : "", (uri->flags&ST_SHDESC) ? (uri->desc ? uri->desc : global.desc) : "",
+			     pid, relative_pid, global.nbproc,
 			     up / 86400, (up % 86400) / 3600,
 			     (up % 3600) / 60, (up % 60),
 			     global.rlimit_memmax ? ultoa(global.rlimit_memmax) : "unlimited",
@@ -999,11 +1003,13 @@
 		if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) {
 			/* print a new table */
 			chunk_printf(&msg,
-				     "<table cols=\"29\" class=\"tbl\" width=\"100%%\">\n"
+				     "<table class=\"tbl\" width=\"100%%\">\n"
 				     "<tr align=\"center\" class=\"titre\">"
-				     "<th colspan=2 class=\"pxname\">%s</th>"
-				     "<th colspan=27 class=\"empty\"></th>"
+				     "<th class=\"pxname\" width=\"10%%\">%s</th>"
+				     "<th class=\"%s\" width=\"90%%\">%s</th>"
 				     "</tr>\n"
+				     "</table>\n"
+				     "<table cols=\"29\" class=\"tbl\" width=\"100%%\">\n"
 				     "<tr align=\"center\" class=\"titre\">"
 				     "<th rowspan=2></th>"
 				     "<th colspan=3>Queue</th>"
@@ -1022,7 +1028,8 @@
 				     "<th>Bck</th><th>Chk</th><th>Dwn</th><th>Dwntme</th>"
 				     "<th>Thrtle</th>\n"
 				     "</tr>",
-				     px->id);
+				     px->id,
+				     px->desc ? "desc" : "empty", px->desc ? px->desc : "");
 
 			if (buffer_write_chunk(rep, &msg) >= 0)
 				return 0;
diff --git a/src/haproxy.c b/src/haproxy.c
index d426e67..b5e1dc9 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -677,6 +677,9 @@
 		printf("Using %s() as the polling mechanism.\n", cur_poller.name);
 	}
 
+	if (!global.node)
+		global.node = strdup(hostname);
+
 }
 
 void deinit(void)
@@ -843,6 +846,8 @@
 
 		free(uap->uri_prefix);
 		free(uap->auth_realm);
+		free(uap->node);
+		free(uap->desc);
 
 		while (uap->users) {
 			user = uap->users;
@@ -857,6 +862,8 @@
 
 	free(global.chroot);  global.chroot = NULL;
 	free(global.pidfile); global.pidfile = NULL;
+	free(global.node);    global.node = NULL;
+	free(global.desc);    global.desc = NULL;
 	free(fdtab);          fdtab   = NULL;
 	free(oldpids);        oldpids = NULL;
 
diff --git a/src/uri_auth.c b/src/uri_auth.c
index ba029a6..3cfc445 100644
--- a/src/uri_auth.c
+++ b/src/uri_auth.c
@@ -110,26 +110,71 @@
 }
 
 /*
- * Returns a default uri_auth with <node-name> set as the node name.
+ * Returns a default uri_auth with ST_SHNODE flag enabled and
+ * <node> set as the name if it is not empty.
  * Uses the pointer provided if not NULL and not initialized.
  */
-struct uri_auth *stats_set_node_name(struct uri_auth **root, char *name)
+struct uri_auth *stats_set_node(struct uri_auth **root, char *name)
 {
 	struct uri_auth *u;
-	char *name_copy;
+	char *node_copy = NULL;
 
-	if ((name_copy = strdup(name)) == NULL)
-		goto out_realm;
+	if (name && *name) {
+		node_copy = strdup(name);
+		if (node_copy == NULL)
+			goto out_realm;
+	}
 	
 	if ((u = stats_check_init_uri_auth(root)) == NULL)
 		goto out_u;
+
+	if (!stats_set_flag(root, ST_SHNODE))
+		goto out_u;
+
+	if (node_copy) {	
+		free(u->node);
+		u->node = node_copy;
+	}
+
+	return u;
+
+ out_u:
+	free(node_copy);
+ out_realm:
+	return NULL;
+}
+
+/*
+ * Returns a default uri_auth with ST_SHDESC flag enabled and
+ * <description> set as the desc if it is not empty.
+ * Uses the pointer provided if not NULL and not initialized.
+ */
+struct uri_auth *stats_set_desc(struct uri_auth **root, char *desc)
+{
+	struct uri_auth *u;
+	char *desc_copy = NULL;
+
+	if (desc && *desc) {
+		desc_copy = strdup(desc);
+		if (desc_copy == NULL)
+			goto out_realm;
+	}
 	
-	free(u->node_name);
-	u->node_name = name_copy;
+	if ((u = stats_check_init_uri_auth(root)) == NULL)
+		goto out_u;
+
+	if (!stats_set_flag(root, ST_SHDESC))
+		goto out_u;
+
+	if (desc_copy) {
+		free(u->desc);
+		u->desc = desc_copy;
+	}
+
 	return u;
 
  out_u:
-	free(name_copy);
+	free(desc_copy);
  out_realm:
 	return NULL;
 }