#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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 . * if 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. * * 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. * * and specify the location of the parsed configuration. * * 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. * * 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 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);