diff --git a/Makefile b/Makefile index 05c79d02bf..580cf6f9ea 100644 --- a/Makefile +++ b/Makefile @@ -1021,6 +1021,9 @@ admin/dyncookie/dyncookie: admin/dyncookie/dyncookie.o dev/flags/flags: dev/flags/flags.o $(cmd_LD) $(LDFLAGS) -o $@ $^ $(LDOPTS) +dev/haring/haring: dev/haring/haring.o + $(cmd_LD) $(LDFLAGS) -o $@ $^ $(LDOPTS) + dev/hpack/%: dev/hpack/%.o $(cmd_LD) $(LDFLAGS) -o $@ $^ $(LDOPTS) @@ -1098,7 +1101,7 @@ clean: $(Q)rm -f admin/iprange/iprange admin/iprange/ip6range admin/halog/halog $(Q)rm -f admin/dyncookie/dyncookie $(Q)rm -f dev/*/*.[oas] - $(Q)rm -f dev/flags/flags dev/poll/poll dev/tcploop/tcploop + $(Q)rm -f dev/flags/flags dev/haring/haring dev/poll/poll dev/tcploop/tcploop $(Q)rm -f dev/hpack/decode dev/hpack/gen-enc dev/hpack/gen-rht $(Q)rm -f dev/qpack/decode diff --git a/dev/haring/README b/dev/haring/README new file mode 100644 index 0000000000..69d08a86c9 --- /dev/null +++ b/dev/haring/README @@ -0,0 +1,4 @@ +This needs to be built from the top makefile, for example : + + make dev/haring/haring + diff --git a/dev/haring/haring.c b/dev/haring/haring.c new file mode 100644 index 0000000000..65a735bd8b --- /dev/null +++ b/dev/haring/haring.c @@ -0,0 +1,230 @@ +/* + * post-mortem ring reader for haproxy + * + * Copyright (C) 2022 Willy Tarreau + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +int force = 0; // force access to a different layout + +/* display the message and exit with the code */ +__attribute__((noreturn)) void die(int code, const char *format, ...) +{ + va_list args; + + if (format) { + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + } + exit(code); +} + +/* display the usage message and exit with the code */ +__attribute__((noreturn)) void usage(int code, const char *arg0) +{ + die(code, + "Usage: %s [options]* \n" + "\n" + "options :\n" + " -f : force accessing a non-matching layout for 'ring struct'\n" + "\n" + "", arg0); +} + +/* This function dumps all events from the ring whose pointer is in into + * the appctx's output buffer, and takes from the seek offset into the + * buffer's history (0 for oldest known event). It looks at for boolean + * options: bit0 means it must wait for new data or any key to be pressed. Bit1 + * means it must seek directly to the end to wait for new contents. It returns + * 0 if the output buffer or events are missing is full and it needs to be + * called again, otherwise non-zero. It is meant to be used with + * cli_release_show_ring() to clean up. + */ +int dump_ring(struct ring *ring, size_t ofs, int flags) +{ + struct buffer buf; + uint64_t msg_len = 0; + size_t len, cnt; + const char *blk1, *blk2; + size_t len1, len2; + + /* Explanation: the storage area in the writing process starts after + * the end of the structure. Since the whole area is mmapped(), we know + * it starts at 0 mod 4096, hence the buf->area pointer's 12 LSB point + * to the relative offset of the storage area. As there will always be + * users using the wrong version of the tool with a dump, we need to + * run a few checks first. After that we'll create our own buffer + * descriptor matching that area. + */ + if ((((long)ring->buf.area) & 4095) != sizeof(*ring)) { + if (!force) { + fprintf(stderr, "FATAL: header in file is %ld bytes long vs %ld expected!\n", + (((long)ring->buf.area) & 4095), + (long)sizeof(*ring)); + exit(1); + } + else { + fprintf(stderr, "WARNING: header in file is %ld bytes long vs %ld expected!\n", + (((long)ring->buf.area) & 4095), + (long)sizeof(*ring)); + } + /* maybe we could emit a warning at least ? */ + } + + /* Now make our own buffer pointing to that area */ + buf = b_make(((void *)ring + (((long)ring->buf.area) & 4095)), + ring->buf.size, ring->buf.head, ring->buf.data); + + /* explanation for the initialization below: it would be better to do + * this in the parsing function but this would occasionally result in + * dropped events because we'd take a reference on the oldest message + * and keep it while being scheduled. Thus instead let's take it the + * first time we enter here so that we have a chance to pass many + * existing messages before grabbing a reference to a location. This + * value cannot be produced after initialization. + */ + if (unlikely(ofs == ~0)) { + ofs = 0; + + /* going to the end means looking at tail-1 */ + if (flags & RING_WF_SEEK_NEW) + ofs += b_data(&buf) - 1; + + //HA_ATOMIC_INC(b_peek(&buf, ofs)); + ofs += ring->ofs; + } + + while (1) { + //HA_RWLOCK_RDLOCK(LOGSRV_LOCK, &ring->lock); + + /* we were already there, adjust the offset to be relative to + * the buffer's head and remove us from the counter. + */ + ofs -= ring->ofs; + if (ofs >= buf.size) { + fprintf(stderr, "FATAL error at %d\n", __LINE__); + return 1; + } + //HA_ATOMIC_DEC(b_peek(&buf, ofs)); + + /* in this loop, ofs always points to the counter byte that precedes + * the message so that we can take our reference there if we have to + * stop before the end. + */ + while (ofs + 1 < b_data(&buf)) { + cnt = 1; + len = b_peek_varint(&buf, ofs + cnt, &msg_len); + if (!len) + break; + cnt += len; + + if (msg_len + ofs + cnt + 1 > buf.data) { + fprintf(stderr, "FATAL error at %d\n", __LINE__); + return 1; + } + + len = b_getblk_nc(&buf, &blk1, &len1, &blk2, &len2, ofs + cnt, msg_len); + if (len > 0 && len1) + fwrite(blk1, len1, 1, stdout); + if (len > 1 && len2) + fwrite(blk2, len2, 1, stdout); + if (len) + putchar('\n'); + + ofs += cnt + msg_len; + } + + //HA_ATOMIC_INC(b_peek(&buf, ofs)); + ofs += ring->ofs; + //HA_RWLOCK_RDUNLOCK(LOGSRV_LOCK, &ring->lock); + + if (!(flags & RING_WF_WAIT_MODE)) + break; + + /* pause 10ms before checking for new stuff */ + usleep(10000); + } + return 0; +} + +int main(int argc, char **argv) +{ + struct ring *ring; + struct stat statbuf; + const char *arg0; + int fd; + + arg0 = argv[0]; + while (argc > 1 && argv[1][0] == '-') { + argc--; argv++; + if (strcmp(argv[0], "-f") == 0) + force = 1; + else if (strcmp(argv[0], "--") == 0) + break; + else + usage(1, arg0); + } + + if (argc < 2) + usage(1, arg0); + + fd = open(argv[1], O_RDONLY); + if (fd < 0) { + perror("open()"); + return 1; + } + + if (fstat(fd, &statbuf) < 0) { + perror("fstat()"); + return 1; + } + + ring = mmap(NULL, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0); + close(fd); + + if (ring == MAP_FAILED) { + perror("mmap()"); + return 1; + } + + return dump_ring(ring, ~0, 0); +} + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */