summaryrefslogtreecommitdiffstats
path: root/src/errors.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/errors.c567
1 files changed, 567 insertions, 0 deletions
diff --git a/src/errors.c b/src/errors.c
new file mode 100644
index 0000000..7a2d14a
--- /dev/null
+++ b/src/errors.c
@@ -0,0 +1,567 @@
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syslog.h>
+
+#include <haproxy/api.h>
+#include <haproxy/applet-t.h>
+#include <haproxy/buf.h>
+#include <haproxy/cli.h>
+#include <haproxy/errors.h>
+#include <haproxy/global.h>
+#include <haproxy/obj_type.h>
+#include <haproxy/ring.h>
+#include <haproxy/tools.h>
+#include <haproxy/version.h>
+
+/* A global buffer used to store all startup alerts/warnings. It will then be
+ * retrieve on the CLI. */
+struct ring *startup_logs = NULL;
+uint tot_warnings = 0;
+#ifdef USE_SHM_OPEN
+static struct ring *shm_startup_logs = NULL;
+#endif
+
+/* A thread local buffer used to store all alerts/warnings. It can be used to
+ * retrieve them for CLI commands after startup.
+ */
+#define USER_MESSAGES_BUFSIZE 1024
+static THREAD_LOCAL struct buffer usermsgs_buf = BUF_NULL;
+
+/* A thread local context used for stderr output via ha_alert/warning/notice/diag.
+ */
+#define USERMSGS_CTX_BUFSIZE PATH_MAX
+static THREAD_LOCAL struct usermsgs_ctx usermsgs_ctx = { .str = BUF_NULL, };
+
+#ifdef USE_SHM_OPEN
+
+/* initialise an SHM for the startup logs and return its fd */
+static int startup_logs_new_shm()
+{
+ char *path = NULL;
+ int fd = -1;
+ int flags;
+
+ /* create a unique path per PID so we don't collide with another
+ process */
+ memprintf(&path, "/haproxy_startup_logs_%d", getpid());
+ fd = shm_open(path, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
+ if (fd == -1)
+ goto error;
+ shm_unlink(path);
+ ha_free(&path);
+
+ if (ftruncate(fd, STARTUP_LOG_SIZE) == -1)
+ goto error;
+
+ flags = fcntl(fd, F_GETFD);
+ if (flags == -1)
+ goto error;
+ flags &= ~FD_CLOEXEC;
+ flags = fcntl(fd, F_SETFD, flags);
+ if (flags == -1)
+ goto error;
+
+ return fd;
+error:
+ if (fd != -1) {
+ close(fd);
+ fd = -1;
+ }
+ return fd;
+}
+
+/* mmap a startup-logs from a <fd>.
+ * if <new> is set to one, initialize the buffer.
+ * Returns the ring.
+ */
+static struct ring *startup_logs_from_fd(int fd, int new)
+{
+ char *area;
+ struct ring *r = NULL;
+
+ if (fd == -1)
+ goto error;
+
+ area = mmap(NULL, STARTUP_LOG_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (area == MAP_FAILED || area == NULL)
+ goto error;
+
+ if (new)
+ r = ring_make_from_area(area, STARTUP_LOG_SIZE);
+ else
+ r = ring_cast_from_area(area);
+
+ if (r == NULL)
+ goto error;
+
+ shm_startup_logs = r; /* save the ptr so we can unmap later */
+
+ return r;
+error:
+ return NULL;
+}
+
+/*
+ * Use a shm across reexec of the master.
+ *
+ * During the startup of the master, a shm_open must be done and the FD saved
+ * into the HAPROXY_STARTUPLOGS_FD environment variable.
+ *
+ * When forking workers, the child must use a copy of the shm, not the shm itself.
+ *
+ * Once in wait mode, the shm must be copied and closed.
+ *
+ */
+void startup_logs_init()
+{
+ struct ring *r = NULL;
+ char *str_fd, *endptr;
+ int fd = -1;
+
+ str_fd = getenv("HAPROXY_STARTUPLOGS_FD");
+ if (str_fd) {
+ fd = strtol(str_fd, &endptr, 10);
+ if (*endptr != '\0')
+ goto error;
+ unsetenv("HAPROXY_STARTUPLOGS_FD");
+ }
+
+ /* during startup, or just after a reload.
+ * Note: the WAIT_ONLY env variable must be
+ * check in case of an early call */
+ if (!(global.mode & MODE_MWORKER_WAIT) &&
+ getenv("HAPROXY_MWORKER_WAIT_ONLY") == NULL) {
+ if (fd != -1)
+ close(fd);
+
+ fd = startup_logs_new_shm();
+ if (fd == -1)
+ goto error;
+
+ r = startup_logs_from_fd(fd, 1);
+ if (!r)
+ goto error;
+
+ str_fd = NULL;
+ memprintf(&str_fd, "%d", fd);
+ setenv("HAPROXY_STARTUPLOGS_FD", str_fd, 1);
+ ha_free(&str_fd);
+
+ } else {
+ /* in wait mode, copy the shm to an allocated buffer */
+ struct ring *prev = NULL;
+
+ if (fd == -1)
+ goto error;
+
+ prev = startup_logs_from_fd(fd, 0);
+ if (!prev)
+ goto error;
+
+ r = startup_logs_dup(prev);
+ if (!r)
+ goto error;
+ startup_logs_free(prev);
+ close(fd);
+ }
+
+ startup_logs = r;
+
+ return;
+error:
+ if (fd != -1)
+ close(fd);
+ /* couldn't get a mmap to work */
+ startup_logs = ring_new(STARTUP_LOG_SIZE);
+
+}
+
+#else /* ! USE_SHM_OPEN */
+
+void startup_logs_init()
+{
+ startup_logs = ring_new(STARTUP_LOG_SIZE);
+}
+
+#endif
+
+/* free the startup logs, unmap if it was an shm */
+void startup_logs_free(struct ring *r)
+{
+#ifdef USE_SHM_OPEN
+ if (r == shm_startup_logs)
+ munmap(r, STARTUP_LOG_SIZE);
+ else
+#endif /* ! USE_SHM_OPEN */
+ ring_free(r);
+}
+
+/* duplicate a startup logs which was previously allocated in a shm */
+struct ring *startup_logs_dup(struct ring *src)
+{
+ struct ring *dst = NULL;
+
+ /* must use the size of the previous buffer */
+ dst = ring_new(b_size(&src->buf));
+ if (!dst)
+ goto error;
+
+ b_reset(&dst->buf);
+ b_ncat(&dst->buf, &src->buf, b_data(&src->buf));
+error:
+ return dst;
+}
+
+/* Put msg in usermsgs_buf.
+ *
+ * The message should not be terminated by a newline because this function
+ * manually insert it.
+ *
+ * If there is not enough room in the buffer, the message is silently discarded.
+ * Do not forget to frequently clear the buffer.
+ */
+static void usermsgs_put(const struct ist *msg)
+{
+ /* Allocate the buffer if not already done. */
+ if (unlikely(b_is_null(&usermsgs_buf))) {
+ usermsgs_buf.area = malloc(USER_MESSAGES_BUFSIZE * sizeof(char));
+ if (usermsgs_buf.area)
+ usermsgs_buf.size = USER_MESSAGES_BUFSIZE;
+ }
+
+ if (likely(!b_is_null(&usermsgs_buf))) {
+ if (b_room(&usermsgs_buf) >= msg->len + 2) {
+ /* Insert the message + newline. */
+ b_putblk(&usermsgs_buf, msg->ptr, msg->len);
+ b_putchr(&usermsgs_buf, '\n');
+ /* Insert NUL outside of the buffer. */
+ *b_tail(&usermsgs_buf) = '\0';
+ }
+ }
+}
+
+/* Clear the user messages log buffer.
+ *
+ * <prefix> will set the local-thread context appended to every output
+ * following this call. It can be NULL if not necessary.
+ */
+void usermsgs_clr(const char *prefix)
+{
+ if (likely(!b_is_null(&usermsgs_buf))) {
+ b_reset(&usermsgs_buf);
+ usermsgs_buf.area[0] = '\0';
+ }
+
+ usermsgs_ctx.prefix = prefix;
+}
+
+/* Check if the user messages buffer is empty. */
+int usermsgs_empty(void)
+{
+ return !!(b_is_null(&usermsgs_buf) || !b_data(&usermsgs_buf));
+}
+
+/* Return the messages log buffer content. */
+const char *usermsgs_str(void)
+{
+ if (unlikely(b_is_null(&usermsgs_buf)))
+ return "";
+
+ return b_head(&usermsgs_buf);
+}
+
+/* Set thread-local context infos to prefix forthcoming stderr output during
+ * configuration parsing.
+ *
+ * <file> and <line> specify the location of the parsed configuration.
+ *
+ * <obj> can be of various types. If not NULL, the string prefix generated will
+ * depend on its type.
+ */
+void set_usermsgs_ctx(const char *file, int line, enum obj_type *obj)
+{
+ usermsgs_ctx.file = file;
+ usermsgs_ctx.line = line;
+ usermsgs_ctx.obj = obj;
+}
+
+/* Set thread-local context infos to prefix forthcoming stderr output. It will
+ * be set as a complement to possibly already defined file/line.
+ *
+ * <obj> can be of various types. If not NULL, the string prefix generated will
+ * depend on its type.
+ */
+void register_parsing_obj(enum obj_type *obj)
+{
+ usermsgs_ctx.obj = obj;
+}
+
+/* Reset thread-local context infos for stderr output. */
+void reset_usermsgs_ctx(void)
+{
+ usermsgs_ctx.file = NULL;
+ usermsgs_ctx.line = 0;
+ usermsgs_ctx.obj = NULL;
+}
+
+static void generate_usermsgs_ctx_str(void)
+{
+ struct usermsgs_ctx *ctx = &usermsgs_ctx;
+ void *area;
+ int ret;
+
+ if (unlikely(b_is_null(&ctx->str))) {
+ area = calloc(USERMSGS_CTX_BUFSIZE, sizeof(*area));
+ if (area)
+ ctx->str = b_make(area, USERMSGS_CTX_BUFSIZE, 0, 0);
+ }
+
+ if (likely(!b_is_null(&ctx->str))) {
+ b_reset(&ctx->str);
+
+ if (ctx->prefix) {
+ ret = snprintf(b_tail(&ctx->str), b_room(&ctx->str),
+ "%s : ", ctx->prefix);
+ b_add(&ctx->str, MIN(ret, b_room(&ctx->str)));
+ }
+
+ if (ctx->file) {
+ ret = snprintf(b_tail(&ctx->str), b_room(&ctx->str),
+ "[%s:%d] : ", ctx->file, ctx->line);
+ b_add(&ctx->str, MIN(ret, b_room(&ctx->str)));
+ }
+
+ switch (obj_type(ctx->obj)) {
+ case OBJ_TYPE_SERVER:
+ ret = snprintf(b_tail(&ctx->str), b_room(&ctx->str),
+ "'server %s/%s' : ",
+ __objt_server(ctx->obj)->proxy->id,
+ __objt_server(ctx->obj)->id);
+ b_add(&ctx->str, MIN(ret, b_room(&ctx->str)));
+ break;
+
+ case OBJ_TYPE_NONE:
+ default:
+ break;
+ }
+
+ if (!b_data(&ctx->str))
+ snprintf(b_tail(&ctx->str), b_room(&ctx->str), "%s", "");
+ }
+}
+
+/* Generic function to display messages prefixed by a label */
+static void print_message(int use_usermsgs_ctx, const char *label, const char *fmt, va_list argp)
+{
+ struct ist msg_ist = IST_NULL;
+ char *head, *parsing_str, *msg;
+ char prefix[11]; // '[' + 8 chars + ']' + 0.
+
+ *prefix = '[';
+ strncpy(prefix + 1, label, sizeof(prefix) - 2);
+ msg = prefix + strlen(prefix);
+ *msg++ = ']';
+ while (msg < prefix + sizeof(prefix) - 1)
+ *msg++ = ' ';
+ *msg = 0;
+
+ head = parsing_str = msg = NULL;
+ memprintf(&head, "%s (%u) : ", prefix, (uint)getpid());
+ memvprintf(&msg, fmt, argp);
+
+ /* trim the trailing '\n' */
+ msg_ist = ist(msg);
+ if (msg_ist.len > 0 && msg_ist.ptr[msg_ist.len - 1] == '\n')
+ msg_ist.len--;
+
+ if (use_usermsgs_ctx) {
+ generate_usermsgs_ctx_str();
+ parsing_str = b_head(&usermsgs_ctx.str);
+ reset_usermsgs_ctx();
+ }
+ else {
+ parsing_str = "";
+ }
+
+ if (global.mode & MODE_STARTING) {
+ if (unlikely(!startup_logs))
+ startup_logs_init();
+
+ if (likely(startup_logs)) {
+ struct ist m[3];
+
+ m[0] = ist(head);
+ m[1] = ist(parsing_str);
+ m[2] = msg_ist;
+
+ ring_write(startup_logs, ~0, 0, 0, m, 3);
+ }
+ }
+ else {
+ usermsgs_put(&msg_ist);
+ }
+ if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)) {
+ fprintf(stderr, "%s%s%s", head, parsing_str, msg);
+ fflush(stderr);
+ }
+
+ free(head);
+ free(msg);
+}
+
+static void print_message_args(int use_usermsgs_ctx, const char *label, const char *fmt, ...)
+{
+ va_list argp;
+ va_start(argp, fmt);
+ print_message(use_usermsgs_ctx, label, fmt, argp);
+ va_end(argp);
+}
+
+/*
+ * Display a notice with the happroxy version and executable path when the
+ * first message is emitted in starting mode.
+ */
+static void warn_exec_path()
+{
+ if (!(warned & WARN_EXEC_PATH) && (global.mode & MODE_STARTING)) {
+ const char *path = get_exec_path();
+
+ warned |= WARN_EXEC_PATH;
+ print_message_args(0, "NOTICE", "haproxy version is %s\n", haproxy_version);
+ if (path)
+ print_message_args(0, "NOTICE", "path to executable is %s\n", path);
+ }
+}
+
+/*
+ * Displays the message on stderr with the pid.
+ */
+void ha_alert(const char *fmt, ...)
+{
+ va_list argp;
+
+ warn_exec_path();
+ va_start(argp, fmt);
+ print_message(1, "ALERT", fmt, argp);
+ va_end(argp);
+}
+
+/*
+ * Displays the message on stderr with the pid.
+ */
+void ha_warning(const char *fmt, ...)
+{
+ va_list argp;
+
+ warned |= WARN_ANY;
+ HA_ATOMIC_INC(&tot_warnings);
+
+ warn_exec_path();
+ va_start(argp, fmt);
+ print_message(1, "WARNING", fmt, argp);
+ va_end(argp);
+}
+
+/*
+ * Variant of _ha_diag_warning with va_list.
+ * Use it only if MODE_DIAG has been previously checked.
+ */
+void _ha_vdiag_warning(const char *fmt, va_list argp)
+{
+ warned |= WARN_ANY;
+ HA_ATOMIC_INC(&tot_warnings);
+
+ warn_exec_path();
+ print_message(1, "DIAG", fmt, argp);
+}
+
+/*
+ * Output a diagnostic warning.
+ * Use it only if MODE_DIAG has been previously checked.
+ */
+void _ha_diag_warning(const char *fmt, ...)
+{
+ va_list argp;
+
+ va_start(argp, fmt);
+ _ha_vdiag_warning(fmt, argp);
+ va_end(argp);
+}
+
+/*
+ * Output a diagnostic warning. Do nothing of MODE_DIAG is not on.
+ */
+void ha_diag_warning(const char *fmt, ...)
+{
+ va_list argp;
+
+ if (global.mode & MODE_DIAG) {
+ va_start(argp, fmt);
+ _ha_vdiag_warning(fmt, argp);
+ va_end(argp);
+ }
+}
+
+/*
+ * Displays the message on stderr with the pid.
+ */
+void ha_notice(const char *fmt, ...)
+{
+ va_list argp;
+
+ va_start(argp, fmt);
+ print_message(1, "NOTICE", fmt, argp);
+ va_end(argp);
+}
+
+/*
+ * Displays the message on <out> only if quiet mode is not set.
+ */
+void qfprintf(FILE *out, const char *fmt, ...)
+{
+ va_list argp;
+
+ if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)) {
+ va_start(argp, fmt);
+ vfprintf(out, fmt, argp);
+ fflush(out);
+ va_end(argp);
+ }
+}
+
+
+/* parse the "show startup-logs" command, returns 1 if a message is returned, otherwise zero */
+static int cli_parse_show_startup_logs(char **args, char *payload, struct appctx *appctx, void *private)
+{
+ if (!cli_has_level(appctx, ACCESS_LVL_OPER))
+ return 1;
+
+ if (!startup_logs)
+ return cli_msg(appctx, LOG_INFO, "\n"); // nothing to print
+
+ return ring_attach_cli(startup_logs, appctx, 0);
+}
+
+/* register cli keywords */
+static struct cli_kw_list cli_kws = {{ },{
+ { { "show", "startup-logs", NULL }, "show startup-logs : report logs emitted during HAProxy startup", cli_parse_show_startup_logs, NULL, NULL, NULL, ACCESS_MASTER },
+ {{},}
+}};
+
+INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
+
+
+static void deinit_errors_buffers()
+{
+ ring_free(_HA_ATOMIC_XCHG(&startup_logs, NULL));
+ ha_free(&usermsgs_buf.area);
+ ha_free(&usermsgs_ctx.str.area);
+}
+
+/* errors might be used in threads and even before forking, thus 2 deinit */
+REGISTER_PER_THREAD_FREE(deinit_errors_buffers);
+REGISTER_POST_DEINIT(deinit_errors_buffers);