| #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); |
| } |
| |
| 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. Overrides the quiet |
| * mode during startup. |
| */ |
| void ha_alert(const char *fmt, ...) |
| { |
| va_list argp; |
| |
| if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE) || |
| !(global.mode & MODE_STARTING)) { |
| 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); |
| |
| if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE) || |
| !(global.mode & MODE_STARTING)) { |
| 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; |
| |
| if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE) || |
| !(global.mode & MODE_STARTING)) { |
| 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); |