diff --git a/doc/configuration.txt b/doc/configuration.txt index 6d1917c44..678d0af32 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -3580,6 +3580,33 @@ servers or traces. ring Creates a new ring-buffer with name . +backing-file + 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 The description is an optional description string of the ring. It will appear on CLI. By default, is reused to fill this field. diff --git a/include/haproxy/sink-t.h b/include/haproxy/sink-t.h index a0de3d15a..b5138c85d 100644 --- a/include/haproxy/sink-t.h +++ b/include/haproxy/sink-t.h @@ -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) diff --git a/src/sink.c b/src/sink.c index 04bc8c87b..5432c4db7 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 +#include +#include + #include #include #include @@ -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 for size and to use it as a backing store + * for 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);