diff --git a/Makefile b/Makefile index 65d8d0c1c..9123e0261 100644 --- a/Makefile +++ b/Makefile @@ -57,6 +57,7 @@ # USE_MEMORY_PROFILING : enable the memory profiler. Linux-glibc only. # USE_LIBATOMIC : force to link with/without libatomic. Automatic. # USE_PTHREAD_EMULATION: replace pthread's rwlocks with ours +# USE_SHM_OPEN : use shm_open() for the startup-logs # # Options can be forced by specifying "USE_xxx=1" or can be disabled by using # "USE_xxx=" (empty string). The list of enabled and disabled options for a @@ -345,7 +346,8 @@ use_opts = USE_EPOLL USE_KQUEUE USE_NETFILTER \ USE_CLOSEFROM USE_ZLIB USE_SLZ USE_CPU_AFFINITY USE_TFO USE_NS \ USE_DL USE_RT USE_DEVICEATLAS USE_51DEGREES USE_WURFL USE_SYSTEMD \ USE_OBSOLETE_LINKER USE_PRCTL USE_PROCCTL USE_THREAD_DUMP \ - USE_EVPORTS USE_OT USE_QUIC USE_PROMEX USE_MEMORY_PROFILING + USE_EVPORTS USE_OT USE_QUIC USE_PROMEX USE_MEMORY_PROFILING \ + USE_SHM_OPEN #### Target system options # Depending on the target platform, some options are set, as well as some @@ -382,7 +384,7 @@ ifeq ($(TARGET),linux-glibc) USE_POLL USE_TPROXY USE_LIBCRYPT USE_DL USE_RT USE_CRYPT_H USE_NETFILTER \ USE_CPU_AFFINITY USE_THREAD USE_EPOLL USE_LINUX_TPROXY \ USE_ACCEPT4 USE_LINUX_SPLICE USE_PRCTL USE_THREAD_DUMP USE_NS USE_TFO \ - USE_GETADDRINFO USE_BACKTRACE) + USE_GETADDRINFO USE_BACKTRACE USE_SHM_OPEN) INSTALL = install -v endif diff --git a/include/haproxy/defaults.h b/include/haproxy/defaults.h index a8e5e5780..7b4992822 100644 --- a/include/haproxy/defaults.h +++ b/include/haproxy/defaults.h @@ -100,7 +100,10 @@ #define MAX_SYSLOG_LEN 1024 #endif -/* 64kB to archive startup-logs seems way more than enough */ +/* 64kB to archive startup-logs seems way more than enough + * /!\ Careful when changing this size, it is used in a shm when exec() from + * mworker to wait mode. + */ #ifndef STARTUP_LOG_SIZE #define STARTUP_LOG_SIZE 65536 #endif diff --git a/include/haproxy/errors.h b/include/haproxy/errors.h index e9ad58835..81a4b2f11 100644 --- a/include/haproxy/errors.h +++ b/include/haproxy/errors.h @@ -43,6 +43,7 @@ #define ERR_CODE (ERR_RETRYABLE|ERR_FATAL|ERR_ABORT) /* mask */ +extern struct ring *startup_logs; /* These codes may be used by config parsing functions which detect errors and * which need to inform the upper layer about them. They are all prefixed with @@ -123,6 +124,10 @@ void ha_notice(const char *fmt, ...) void qfprintf(FILE *out, const char *fmt, ...) __attribute__ ((format(printf, 2, 3))); +void startup_logs_init(); +struct ring *startup_logs_dup(struct ring *src); +void startup_logs_free(struct ring *r); + #endif /* _HAPROXY_ERRORS_H */ /* diff --git a/src/errors.c b/src/errors.c index bd3c271f4..96ed60d2a 100644 --- a/src/errors.c +++ b/src/errors.c @@ -1,5 +1,9 @@ +#include +#include +#include #include #include +#include #include #include @@ -15,7 +19,10 @@ /* A global buffer used to store all startup alerts/warnings. It will then be * retrieve on the CLI. */ -static struct ring *startup_logs = NULL; +struct ring *startup_logs = NULL; +#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. @@ -28,6 +35,185 @@ static THREAD_LOCAL struct buffer usermsgs_buf = BUF_NULL; #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 accross 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; + + 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 @@ -200,7 +386,7 @@ static void print_message(int use_usermsgs_ctx, const char *label, const char *f if (global.mode & MODE_STARTING) { if (unlikely(!startup_logs)) - startup_logs = ring_new(STARTUP_LOG_SIZE); + startup_logs_init(); if (likely(startup_logs)) { struct ist m[3]; @@ -361,7 +547,7 @@ static int cli_parse_show_startup_logs(char **args, char *payload, struct appctx /* 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 }, + { { "show", "startup-logs", NULL }, "show startup-logs : report logs emitted during HAProxy startup", cli_parse_show_startup_logs, NULL, NULL, NULL, ACCESS_MASTER }, {{},} }}; diff --git a/src/haproxy.c b/src/haproxy.c index a95fc21e4..806497062 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -1909,6 +1909,8 @@ static void init(int argc, char **argv) struct pre_check_fct *prcf; int ideal_maxconn; + startup_logs_init(); + if (!init_trash_buffers(1)) { ha_alert("failed to initialize trash buffers.\n"); exit(1); @@ -3431,9 +3433,13 @@ int main(int argc, char **argv) /* the father launches the required number of processes */ if (!(global.mode & MODE_MWORKER_WAIT)) { + struct ring *tmp_startup_logs = NULL; + if (global.mode & MODE_MWORKER) mworker_ext_launch_all(); + /* at this point the worker must have his own startup_logs buffer */ + tmp_startup_logs = startup_logs_dup(startup_logs); ret = fork(); if (ret < 0) { ha_alert("[%s.main()] Cannot fork.\n", argv[0]); @@ -3441,6 +3447,8 @@ int main(int argc, char **argv) exit(1); /* there has been an error */ } else if (ret == 0) { /* child breaks here */ + startup_logs_free(startup_logs); + startup_logs = tmp_startup_logs; /* This one must not be exported, it's internal! */ unsetenv("HAPROXY_MWORKER_REEXEC"); ha_random_jump96(1);