[MEDIUM] implement error dump on unix socket with "show errors"

The new "show errors" command sent on a unix socket will dump
all captured request and response errors for all proxies. It is
also possible to bound the log to frontends and backends whose
ID is passed as an optional parameter.

The output provides information about frontend, backend, server,
session ID, source address, error type, and error position along
with a complete dump of the request or response which has caused
the error.

If a new error scratches the one currently being reported, then
the dump is aborted with a warning message, and processing goes
on to next error.
diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h
index cf9ae29..844879a 100644
--- a/include/proto/dumpstats.h
+++ b/include/proto/dumpstats.h
@@ -49,6 +49,7 @@
 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);
+void stats_dump_errors_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 fb63347..356bdee 100644
--- a/include/types/session.h
+++ b/include/types/session.h
@@ -204,6 +204,14 @@
 		struct {
 			struct bref bref;
 		} sess;
+		struct {
+			int iid;		/* if >= 0, ID of the proxy to filter on */
+			struct proxy *px;	/* current proxy being dumped, NULL = not started yet. */
+			unsigned int buf;	/* buffer being dumped, 0 = req, 1 = rep */
+			unsigned int sid;	/* session ID of error being dumped */
+			int ptr;		/* <0: headers, >=0 : text pointer to restart from */
+			int bol;		/* pointer to beginning of current line */
+		} errors;
 	} 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 b12fbd0..dd047b7 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -1222,6 +1222,212 @@
 	}
 }
 
+/* print a line of error buffer (limited to 70 bytes) to <out>. The format is :
+ * <2 spaces> <offset=5 digits> <space or plus> <space> <70 chars max> <\n>
+ * which is 60 chars per line. Non-printable chars \t, \n, \r and \e are
+ * encoded in C format. Other non-printable chars are encoded "\xHH". Original
+ * lines are respected within the limit of 70 output chars. Lines that are
+ * continuation of a previous truncated line begin with "+" instead of " "
+ * after the offset. The new pointer is returned.
+ */
+static int dump_error_line(struct chunk *out, int size,
+                          struct error_snapshot *err, int *line, int ptr)
+{
+	int end;
+	unsigned char c;
+
+	end = out->len + 80;
+	if (end > size)
+		return ptr;
+
+	chunk_printf(out, size, "  %05d%c ", ptr, (ptr == *line) ? ' ' : '+');
+
+	while (ptr < err->len) {
+		c = err->buf[ptr];
+		if (isprint(c)) {
+			if (out->len > end - 2)
+				break;
+			out->str[out->len++] = c;
+		} else if (c == '\t' || c == '\n' || c == '\r' || c == '\e') {
+			if (out->len > end - 3)
+				break;
+			out->str[out->len++] = '\\';
+			switch (c) {
+			case '\t': c = 't'; break;
+			case '\n': c = 'n'; break;
+			case '\r': c = 'r'; break;
+			case '\e': c = 'e'; break;
+			}
+			out->str[out->len++] = c;
+		} else {
+			if (out->len > end - 5)
+				break;
+			out->str[out->len++] = '\\';
+			out->str[out->len++] = 'x';
+			out->str[out->len++] = hextab[(c >> 4) & 0xF];
+			out->str[out->len++] = hextab[c & 0xF];
+		}
+		if (err->buf[ptr++] == '\n') {
+			/* we had a line break, let's return now */
+			out->str[out->len++] = '\n';
+			*line = ptr;
+			return ptr;
+		}
+	}
+	/* we have an incomplete line, we return it as-is */
+	out->str[out->len++] = '\n';
+	return ptr;
+}
+
+/* This function is called to send output to the response buffer.
+ * It dumps the errors logged in proxies 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_errors_to_buffer(struct session *s, struct buffer *rep)
+{
+	extern const char *monthname[12];
+	struct chunk msg;
+
+	if (unlikely(rep->flags & (BF_WRITE_ERROR|BF_SHUTW))) {
+		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;
+
+	if (!s->data_ctx.errors.px) {
+		/* the function had not been called yet, let's prepare the
+		 * buffer for a response.
+		 */
+		stream_int_retnclose(rep->cons, &msg);
+		s->data_ctx.errors.px = proxy;
+		s->data_ctx.errors.buf = 0;
+		s->data_ctx.errors.bol = 0;
+		s->data_ctx.errors.ptr = -1;
+	}
+
+	/* we have two inner loops here, one for the proxy, the other one for
+	 * the buffer.
+	 */
+	while (s->data_ctx.errors.px) {
+		struct error_snapshot *es;
+
+		if (s->data_ctx.errors.buf == 0)
+			es = &s->data_ctx.errors.px->invalid_req;
+		else
+			es = &s->data_ctx.errors.px->invalid_rep;
+
+		if (!es->when.tv_sec)
+			goto next;
+
+		if (s->data_ctx.errors.iid >= 0 &&
+		    s->data_ctx.errors.px->uuid != s->data_ctx.errors.iid &&
+		    es->oe->uuid != s->data_ctx.errors.iid)
+			goto next;
+
+		if (s->data_ctx.errors.ptr < 0) {
+			/* just print headers now */
+
+			char pn[INET6_ADDRSTRLEN];
+			struct tm tm;
+
+			get_localtime(es->when.tv_sec, &tm);
+			chunk_printf(&msg, sizeof(trash), "\n[%02d/%s/%04d:%02d:%02d:%02d.%03d]",
+				     tm.tm_mday, monthname[tm.tm_mon], tm.tm_year+1900,
+				     tm.tm_hour, tm.tm_min, tm.tm_sec, es->when.tv_usec/1000);
+
+
+			if (es->src.ss_family == AF_INET)
+				inet_ntop(AF_INET,
+					  (const void *)&((struct sockaddr_in *)&es->src)->sin_addr,
+					  pn, sizeof(pn));
+			else
+				inet_ntop(AF_INET6,
+					  (const void *)&((struct sockaddr_in6 *)(&es->src))->sin6_addr,
+					  pn, sizeof(pn));
+
+			switch (s->data_ctx.errors.buf) {
+			case 0:
+				chunk_printf(&msg, sizeof(trash),
+					     " frontend %s (#%d): invalid request\n"
+					     "  src %s, session #%d, backend %s (#%d), server %s (#%d)\n"
+					     "  request length %d bytes, error at position %d:\n\n",
+					     s->data_ctx.errors.px->id, s->data_ctx.errors.px->uuid,
+					     pn, es->sid, es->oe->id, es->oe->uuid,
+					     es->srv ? es->srv->id : "<NONE>",
+					     es->srv ? es->srv->puid : -1,
+					     es->len, es->pos);
+				break;
+			case 1:
+				chunk_printf(&msg, sizeof(trash),
+					     " backend %s (#%d) : invalid response\n"
+					     "  src %s, session #%d, frontend %s (#%d), server %s (#%d)\n"
+					     "  response length %d bytes, error at position %d:\n\n",
+					     s->data_ctx.errors.px->id, s->data_ctx.errors.px->uuid,
+					     pn, es->sid, es->oe->id, es->oe->uuid,
+					     es->srv ? es->srv->id : "<NONE>",
+					     es->srv ? es->srv->puid : -1,
+					     es->len, es->pos);
+				break;
+			}
+
+			if (buffer_write_chunk(rep, &msg) >= 0) {
+				/* Socket buffer full. Let's try again later from the same point */
+				return;
+			}
+			s->data_ctx.errors.ptr = 0;
+			s->data_ctx.errors.sid = es->sid;
+		}
+
+		if (s->data_ctx.errors.sid != es->sid) {
+			/* the snapshot changed while we were dumping it */
+			chunk_printf(&msg, sizeof(trash),
+				     "  WARNING! update detected on this snapshot, dump interrupted. Please re-check!\n");
+			if (buffer_write_chunk(rep, &msg) >= 0)
+				return;
+			goto next;
+		}
+
+		/* OK, ptr >= 0, so we have to dump the current line */
+		while (s->data_ctx.errors.ptr < es->len) {
+			int newptr;
+			int newline;
+
+			newline = s->data_ctx.errors.bol;
+			newptr = dump_error_line(&msg, sizeof(trash), es, &newline, s->data_ctx.errors.ptr);
+			if (newptr == s->data_ctx.errors.ptr)
+				return;
+
+			if (buffer_write_chunk(rep, &msg) >= 0) {
+				/* Socket buffer full. Let's try again later from the same point */
+				return;
+			}
+			s->data_ctx.errors.ptr = newptr;
+			s->data_ctx.errors.bol = newline;
+		};
+	next:
+		s->data_ctx.errors.bol = 0;
+		s->data_ctx.errors.ptr = -1;
+		s->data_ctx.errors.buf++;
+		if (s->data_ctx.errors.buf > 1) {
+			s->data_ctx.errors.buf = 0;
+			s->data_ctx.errors.px = s->data_ctx.errors.px->next;
+		}
+	}
+
+	/* dump complete */
+	buffer_stop_hijack(rep);
+	s->ana_state = STATS_ST_CLOSE;
+}
+
 
 static struct cfg_kw_list cfg_kws = {{ },{
 	{ CFG_GLOBAL, "stats", stats_parse_global },
diff --git a/src/proto_uxst.c b/src/proto_uxst.c
index a3bc98d..4b24209 100644
--- a/src/proto_uxst.c
+++ b/src/proto_uxst.c
@@ -615,6 +615,15 @@
 			s->ana_state = STATS_ST_REP;
 			buffer_install_hijacker(s, s->rep, stats_dump_sess_to_buffer);
 		}
+		else if (strcmp(args[1], "errors") == 0) {
+			if (*args[2])
+				s->data_ctx.errors.iid	= atoi(args[2]);
+			else
+				s->data_ctx.errors.iid	= -1;
+			s->data_ctx.errors.px = NULL;
+			s->ana_state = STATS_ST_REP;
+			buffer_install_hijacker(s, s->rep, stats_dump_errors_to_buffer);
+		}
 		else { /* neither "stat" nor "info" nor "sess" */
 			return 0;
 		}