MINOR: ring: add support for a backing-file

This mmaps a file which will serve as the backing-store for the ring's
contents. The idea is to provide a way to retrieve sensitive information
(last logs, debugging traces) even after the process stops and even after
a possible crash. Right now this was possible by connecting to the CLI
and dumping the contents of the ring live, but this is not handy and
consumes quite a bit of resources before it is needed.

With a backing file, the ring is effectively RAM-mapped file, so that
contents stored there are the same as those found in the file (the OS
doesn't guarantee immediate sync but if the process dies it will be OK).

Note that doing that on a filesystem backed by a physical device is a
bad idea, as it will induce slowdowns at high loads. It's really
important that the device is RAM-based.

Also, this may have security implications: if the file is corrupted by
another process, the storage area could be corrupted, causing haproxy
to crash or to overwrite its own memory. As such this should only be
used for debugging.
diff --git a/src/sink.c b/src/sink.c
index 04bc8c8..5432c4d 100644
--- a/src/sink.c
+++ b/src/sink.c
@@ -18,6 +18,10 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
+#include <sys/mman.h>
+#include <errno.h>
+#include <fcntl.h>
+
 #include <import/ist.h>
 #include <haproxy/api.h>
 #include <haproxy/applet.h>
@@ -817,6 +821,12 @@
 			goto err;
 		}
 
+		if (cfg_sink->store) {
+			ha_alert("parsing [%s:%d] : cannot resize an already mapped file, please specify 'size' before 'backing-file'.\n", file, linenum);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto err;
+		}
+
 		if (size < cfg_sink->ctx.ring->buf.size) {
 			ha_warning("parsing [%s:%d] : ignoring new size '%llu' that is smaller than current size '%llu' for ring '%s'.\n",
 				   file, linenum, (ullong)size, (ullong)cfg_sink->ctx.ring->buf.size, cfg_sink->name);
@@ -830,6 +840,58 @@
 			err_code |= ERR_ALERT | ERR_FATAL;
 			goto err;
 		}
+	}
+	else if (strcmp(args[0], "backing-file") == 0) {
+		/* This tries to mmap file <file> for size <size> and to use it as a backing store
+		 * for ring <ring>. Existing data are delete. NULL is returned on error.
+		 */
+		const char *backing = args[1];
+		size_t size;
+		void *area;
+		int fd;
+
+		if (!cfg_sink || (cfg_sink->type != SINK_TYPE_BUFFER)) {
+			ha_alert("parsing [%s:%d] : 'backing-file' only usable with existing rings.\n", file, linenum);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto err;
+		}
+
+		if (cfg_sink->store) {
+			ha_alert("parsing [%s:%d] : 'backing-file' already specified for ring '%s' (was '%s').\n", file, linenum, cfg_sink->name, cfg_sink->store);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto err;
+		}
+
+		fd = open(backing, O_RDWR | O_CREAT, S_IRUSR|S_IWUSR);
+		if (fd < 0) {
+			ha_alert("parsing [%s:%d] : cannot open backing-file '%s' for ring '%s': %s.\n", file, linenum, backing, cfg_sink->name, strerror(errno));
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto err;
+		}
+
+		size = (cfg_sink->ctx.ring->buf.size + 4095UL) & -4096UL;
+		if (ftruncate(fd, size) != 0) {
+			close(fd);
+			ha_alert("parsing [%s:%d] : could not adjust size of backing-file for ring '%s': %s.\n", file, linenum, cfg_sink->name, strerror(errno));
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto err;
+		}
+
+		area = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+		if (area == MAP_FAILED) {
+			close(fd);
+			ha_alert("parsing [%s:%d] : failed to use '%s' as a backing file for ring '%s': %s.\n", file, linenum, backing, cfg_sink->name, strerror(errno));
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto err;
+		}
+
+		/* we don't need the file anymore */
+		close(fd);
+		cfg_sink->store = strdup(backing);
+
+		/* never fails */
+		ring_free(cfg_sink->ctx.ring);
+		cfg_sink->ctx.ring = ring_make_from_area(area, size);
 	}
 	else if (strcmp(args[0],"server") == 0) {
 		err_code |= parse_server(file, linenum, args, cfg_sink->forward_px, NULL,
@@ -1267,8 +1329,12 @@
 	struct sink *sink, *sb;
 
 	list_for_each_entry_safe(sink, sb, &sink_list, sink_list) {
-		if (sink->type == SINK_TYPE_BUFFER)
-			ring_free(sink->ctx.ring);
+		if (sink->type == SINK_TYPE_BUFFER) {
+			if (sink->store)
+				munmap(sink->ctx.ring->buf.area, sink->ctx.ring->buf.size);
+			else
+				ring_free(sink->ctx.ring);
+		}
 		LIST_DELETE(&sink->sink_list);
 		free(sink->name);
 		free(sink->desc);