mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2025-02-22 21:56:55 +00:00
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.
This commit is contained in:
parent
6df10d872b
commit
0b8e9ceb12
@ -3580,6 +3580,33 @@ servers or traces.
|
||||
ring <ringname>
|
||||
Creates a new ring-buffer with name <ringname>.
|
||||
|
||||
backing-file <path>
|
||||
This replaces the regular memory allocation by a RAM-mapped file to store the
|
||||
ring. This can be useful for collecting traces or logs for post-mortem
|
||||
analysis, without having to attach a slow client to the CLI. Newer contents
|
||||
will automatically replace older ones so that the latest contents are always
|
||||
available. The contents written to the ring will be visible in that file once
|
||||
the process stops (most often they will even be seen very soon after but
|
||||
there is no such guarantee since writes are not synchronous).
|
||||
|
||||
When this option is used, the total storage area is reduced by the size of
|
||||
the "struct ring" that starts at the beginning of the area, and that is
|
||||
required to recover the area's contents. The file will be created with the
|
||||
starting user's ownership, with mode 0600 and will be of the size configured
|
||||
by the "size" directive.
|
||||
|
||||
WARNING: there are stability and security implications in using this feature.
|
||||
First, backing the ring to a slow device (e.g. physical hard drive) may cause
|
||||
perceptible slowdowns during accesses, and possibly even panics if too many
|
||||
threads compete for accesses. Second, an external process modifying the area
|
||||
could cause the haproxy process to crash or to overwrite some of its own
|
||||
memory with traces. Third, if the file system fills up before the ring,
|
||||
writes to the ring may cause the process to crash.
|
||||
|
||||
The information present in this ring are structured and are NOT directly
|
||||
readable using a text editor (even though most of it looks barely readable).
|
||||
The output of this file is only intended for developers.
|
||||
|
||||
description <text>
|
||||
The description is an optional description string of the ring. It will
|
||||
appear on CLI. By default, <name> is reused to fill this field.
|
||||
|
@ -50,6 +50,7 @@ struct sink {
|
||||
struct list sink_list; // position in the sink list
|
||||
char *name; // sink name
|
||||
char *desc; // sink description
|
||||
char *store; // backing-store file when buffer
|
||||
enum log_fmt fmt; // format expected by the sink
|
||||
enum sink_type type; // type of storage
|
||||
uint32_t maxlen; // max message length (truncated above)
|
||||
|
70
src/sink.c
70
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 @@ int cfg_parse_ring(const char *file, int linenum, char **args, int kwm)
|
||||
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);
|
||||
@ -831,6 +841,58 @@ int cfg_parse_ring(const char *file, int linenum, char **args, int kwm)
|
||||
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,
|
||||
SRV_PARSE_PARSE_ADDR|SRV_PARSE_INITIAL_RESOLVE);
|
||||
@ -1267,8 +1329,12 @@ static void sink_deinit()
|
||||
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);
|
||||
|
Loading…
Reference in New Issue
Block a user