[MEDIUM] add support for "show sess" in unix stats socket

It is now possible to list all known sessions by issuing "show sess"
on the unix stats socket. The format is not much evolved but it is
very useful for debugging.

The doc has been updated to reflect the new keyword.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 943158a..596f2ab 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -151,8 +151,10 @@
   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, and more general process
-  information in response to the "show info" command followed by a line feed.
+  "show stat" command followed by a line feed, more general process information
+  in response to the "show info" command followed by a line feed, and a
+  complete list of all existing sessions in response to the "show sess" 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
@@ -4339,7 +4341,9 @@
 
 [to do]
 
+
 2.7) CSV format
+---------------
 
   0. pxname: proxy name
   1. svname: service name (FRONTEND for frontend, BACKEND for backend, any name
@@ -4376,18 +4380,33 @@
  31. tracked: id of proxy/server if tracking is enabled
  32. type (0=frontend, 1=backend, 2=server)
 
+
 2.8) Unix Socket commands
+-------------------------
 
- - "show stat [<iid> <type> <sid>]": dump statistics in the cvs format. By
-   passing id, type and sid it is possible to dump only selected items:
-     - iid is a proxy id, -1 to dump everything
-     - type selects type of dumpable objects: 1 for frontend, 2 for backend, 4 for
-       server, -1 for everything. Values can be ORed, for example:
-          1+2=3   -> frontend+backend.
-          1+2+4=7 -> frontend+backend+server.
-     - sid is a service id, -1 to dump everything from the selected proxy.
+The following commands are supported on the UNIX stats socket ; all of them
+must be terminated by a line feed. It is important to understand that when
+multiple haproxy processes are started on the same sockets, any process may
+pick up the request and will output its own stats.
+
+show stat [<iid> <type> <sid>]
+  Dump statistics in the CSV format. By passing <id>, <type> and <sid>, it is
+  possible to dump only selected items :
+    - <iid> is a proxy ID, -1 to dump everything
+    - <type> selects the type of dumpable objects : 1 for frontends, 2 for
+       backends, 4 for servers, -1 for everything. These values can be ORed,
+       for example:
+          1 + 2     = 3   -> frontend + backend.
+          1 + 2 + 4 = 7   -> frontend + backend + server.
+    - <sid> is a server ID, -1 to dump everything from the selected proxy.
+
+show info
+  Dump info about haproxy status on current process.
+
+show sess
+  Dump all known sessions. Avoid doing this on slow connections as this can
+  be huge.
 
- - "show info": dump info about current haproxy status.
 
 /*
  * Local variables:
diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h
index 8fd3ccb..cf9ae29 100644
--- a/include/proto/dumpstats.h
+++ b/include/proto/dumpstats.h
@@ -48,6 +48,7 @@
 void stats_dump_raw_to_buffer(struct session *s, struct buffer *req);
 int stats_dump_http(struct session *s, struct buffer *rep, struct uri_auth *uri);
 int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri);
+void stats_dump_sess_to_buffer(struct session *s, struct buffer *rep);
 
 
 #endif /* _PROTO_DUMPSTATS_H */
diff --git a/include/types/session.h b/include/types/session.h
index 7630bc3..fb63347 100644
--- a/include/types/session.h
+++ b/include/types/session.h
@@ -201,6 +201,9 @@
 			unsigned int flags;	/* STAT_* */
 			int iid, type, sid;	/* proxy id, type and service id if bounding of stats is enabled */
 		} stats;
+		struct {
+			struct bref bref;
+		} sess;
 	} data_ctx;				/* used by produce_content to dump the stats right now */
 	unsigned int uniq_id;			/* unique ID used for the traces */
 };
diff --git a/src/dumpstats.c b/src/dumpstats.c
index 30cb0ed..f67d217 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -1081,6 +1081,132 @@
 	}
 }
 
+
+/* This function is called to send output to the response buffer.
+ * It dumps the sessions states onto the output buffer <rep>.
+ * Expects to be called with client socket shut down on input.
+ * s->data_ctx must have been zeroed first, and the flags properly set.
+ * It automatically clears the HIJACK bit from the response buffer.
+ */
+void stats_dump_sess_to_buffer(struct session *s, struct buffer *rep)
+{
+	struct chunk msg;
+
+	if (unlikely(rep->flags & (BF_WRITE_ERROR|BF_SHUTW))) {
+		/* If we're forced to shut down, we might have to remove our
+		 * reference to the last session being dumped.
+		 */
+		if (s->data_state == DATA_ST_LIST) {
+			if (!LIST_ISEMPTY(&s->data_ctx.sess.bref.users))
+				LIST_DEL(&s->data_ctx.sess.bref.users);
+		}
+		s->data_state = DATA_ST_FIN;
+		buffer_stop_hijack(rep);
+		s->ana_state = STATS_ST_CLOSE;
+		return;
+	}
+
+	if (s->ana_state != STATS_ST_REP)
+		return;
+
+	msg.len = 0;
+	msg.str = trash;
+
+	switch (s->data_state) {
+	case DATA_ST_INIT:
+		/* the function had not been called yet, let's prepare the
+		 * buffer for a response. We initialize the current session
+		 * pointer to the first in the global list.
+		 */
+		stream_int_retnclose(rep->cons, &msg);
+		LIST_INIT(&s->data_ctx.sess.bref.users);
+		s->data_ctx.sess.bref.ref = sessions.n;
+		s->data_state = DATA_ST_LIST;
+		/* fall through */
+
+	case DATA_ST_LIST:
+		while (s->data_ctx.sess.bref.ref != &sessions) {
+			char pn[INET6_ADDRSTRLEN + strlen(":65535")];
+			struct session *curr_sess;
+
+			curr_sess = LIST_ELEM(s->data_ctx.sess.bref.ref, struct session *, list);
+
+			/* first, let's detach the back-ref from a possible previous session */
+			if (!LIST_ISEMPTY(&s->data_ctx.sess.bref.users))
+				LIST_DEL(&s->data_ctx.sess.bref.users);
+
+			chunk_printf(&msg, sizeof(trash),
+				     "%p: proto=%s",
+				     curr_sess,
+				     curr_sess->listener->proto->name);
+
+			switch (curr_sess->listener->proto->sock_family) {
+			case AF_INET:
+				inet_ntop(AF_INET,
+					  (const void *)&((struct sockaddr_in *)&curr_sess->cli_addr)->sin_addr,
+					  pn, sizeof(pn));
+
+				chunk_printf(&msg, sizeof(trash),
+					     " src=%s:%d fe=%s be=%s srv=%s",
+					     pn,
+					     ntohs(((struct sockaddr_in *)&curr_sess->cli_addr)->sin_port),
+					     curr_sess->fe->id,
+					     curr_sess->be->id,
+					     curr_sess->srv ? curr_sess->srv->id : "<none>"
+					     );
+				break;
+			case AF_INET6:
+				inet_ntop(AF_INET6,
+					  (const void *)&((struct sockaddr_in6 *)(&curr_sess->cli_addr))->sin6_addr,
+					  pn, sizeof(pn));
+
+				chunk_printf(&msg, sizeof(trash),
+					     " src=%s:%d fe=%s be=%s srv=%s",
+					     pn,
+					     ntohs(((struct sockaddr_in6 *)&curr_sess->cli_addr)->sin6_port),
+					     curr_sess->fe->id,
+					     curr_sess->be->id,
+					     curr_sess->srv ? curr_sess->srv->id : "<none>"
+					     );
+
+				break;
+			case AF_UNIX:
+				/* no more information to print right now */
+				break;
+			}
+
+			chunk_printf(&msg, sizeof(trash),
+				     " si=(%d,%d) as=%d age=%s",
+				     curr_sess->si[0].state, curr_sess->si[1].state,
+				     curr_sess->ana_state,
+				     human_time(now.tv_sec - curr_sess->logs.tv_accept.tv_sec, 1));
+
+			chunk_printf(&msg, sizeof(trash),
+				     " exp=%s\n",
+				     curr_sess->task->expire ?
+				     human_time(TICKS_TO_MS(tick_remain(now_ms, curr_sess->task->expire)),
+						TICKS_TO_MS(1000)) : "never");
+
+			if (buffer_write_chunk(rep, &msg) >= 0) {
+				/* let's try again later */
+				LIST_ADDQ(&curr_sess->back_refs, &s->data_ctx.sess.bref.users);
+				return;
+			}
+
+			s->data_ctx.sess.bref.ref = curr_sess->list.n;
+		}
+		s->data_state = DATA_ST_FIN;
+		/* fall through */
+
+	default:
+		s->data_state = DATA_ST_FIN;
+		buffer_stop_hijack(rep);
+		s->ana_state = STATS_ST_CLOSE;
+		return;
+	}
+}
+
+
 static struct cfg_kw_list cfg_kws = {{ },{
 	{ CFG_GLOBAL, "stats", stats_parse_global },
 	{ 0, NULL, NULL },
diff --git a/src/proto_uxst.c b/src/proto_uxst.c
index 2543268..e31398a 100644
--- a/src/proto_uxst.c
+++ b/src/proto_uxst.c
@@ -472,6 +472,8 @@
 		memset(&s->logs, 0, sizeof(s->logs));
 		memset(&s->txn, 0, sizeof(s->txn));
 
+		s->logs.tv_accept = now;  /* corrected date for internal use */
+
 		s->data_state = DATA_ST_INIT;
 		s->data_source = DATA_SRC_NONE;
 		s->uniq_id = totalconn;
@@ -611,7 +613,11 @@
 			s->ana_state = STATS_ST_REP;
 			buffer_install_hijacker(s, s->rep, stats_dump_raw_to_buffer);
 		}
+		else if (strcmp(args[1], "sess") == 0) {
+			s->ana_state = STATS_ST_REP;
+			buffer_install_hijacker(s, s->rep, stats_dump_sess_to_buffer);
+		}
-		else { /* neither "stat" nor "info" */
+		else { /* neither "stat" nor "info" nor "sess" */
 			return 0;
 		}
 	}