summaryrefslogtreecommitdiffstats
path: root/src/lib/failures.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/failures.c')
-rw-r--r--src/lib/failures.c998
1 files changed, 998 insertions, 0 deletions
diff --git a/src/lib/failures.c b/src/lib/failures.c
new file mode 100644
index 0000000..083e3b8
--- /dev/null
+++ b/src/lib/failures.c
@@ -0,0 +1,998 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "str.h"
+#include "hostpid.h"
+#include "net.h"
+#include "process-title.h"
+#include "lib-signals.h"
+#include "backtrace-string.h"
+#include "printf-format-fix.h"
+#include "write-full.h"
+#include "time-util.h"
+#include "failures-private.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <time.h>
+#include <poll.h>
+
+#define LOG_TYPE_FLAG_PREFIX_LEN 0x40
+#define LOG_TYPE_FLAG_DISABLE_LOG_PREFIX 0x80
+
+const char *failure_log_type_prefixes[LOG_TYPE_COUNT] = {
+ "Debug: ",
+ "Info: ",
+ "Warning: ",
+ "Error: ",
+ "Fatal: ",
+ "Panic: "
+};
+
+const char *failure_log_type_names[LOG_TYPE_COUNT] = {
+ "debug", "info", "warning", "error", "fatal", "panic"
+};
+
+static int log_fd_write(int fd, const unsigned char *data, size_t len);
+
+static void error_handler_real(const struct failure_context *ctx,
+ const char *format, va_list args);
+
+/* Initialize working defaults */
+static failure_callback_t *fatal_handler ATTR_NORETURN =
+ default_fatal_handler;
+static failure_callback_t *error_handler = default_error_handler;
+static failure_callback_t *info_handler = default_error_handler;
+static failure_callback_t *debug_handler = default_error_handler;
+static void (*failure_exit_callback)(int *) = NULL;
+
+static struct failure_context failure_ctx_debug = { .type = LOG_TYPE_DEBUG };
+static struct failure_context failure_ctx_info = { .type = LOG_TYPE_INFO };
+static struct failure_context failure_ctx_warning = { .type = LOG_TYPE_WARNING };
+static struct failure_context failure_ctx_error = { .type = LOG_TYPE_ERROR };
+
+static int log_fd = STDERR_FILENO, log_info_fd = STDERR_FILENO,
+ log_debug_fd = STDERR_FILENO;
+static char *log_prefix = NULL;
+static char *log_stamp_format = NULL, *log_stamp_format_suffix = NULL;
+static bool failure_ignore_errors = FALSE, log_prefix_sent = FALSE;
+static bool coredump_on_error = FALSE;
+static void log_timestamp_add(const struct failure_context *ctx, string_t *str);
+static void log_prefix_add(const struct failure_context *ctx, string_t *str);
+static int i_failure_send_option_forced(const char *key, const char *value);
+static int internal_send_split(string_t *full_str, size_t prefix_len);
+
+static string_t * ATTR_FORMAT(3, 0) default_format(const struct failure_context *ctx,
+ size_t *prefix_len_r ATTR_UNUSED,
+ const char *format,
+ va_list args)
+{
+ string_t *str = t_str_new(256);
+ log_timestamp_add(ctx, str);
+ log_prefix_add(ctx, str);
+
+ /* make sure there's no %n in there and fix %m */
+ str_vprintfa(str, printf_format_fix(format), args);
+ return str;
+}
+
+static int default_write(enum log_type type, string_t *data, size_t prefix_len ATTR_UNUSED)
+{
+ int fd;
+
+ switch (type) {
+ case LOG_TYPE_DEBUG:
+ fd = log_debug_fd;
+ break;
+ case LOG_TYPE_INFO:
+ fd = log_info_fd;
+ break;
+ default:
+ fd = log_fd;
+ break;
+ }
+ str_append_c(data, '\n');
+ return log_fd_write(fd, str_data(data), str_len(data));
+}
+
+static void default_on_handler_failure(const struct failure_context *ctx)
+{
+ const char *log_type = "info";
+ switch (ctx->type) {
+ case LOG_TYPE_DEBUG:
+ log_type = "debug";
+ /* fall through */
+ case LOG_TYPE_INFO:
+ /* we failed to log to info/debug log, try to log the
+ write error to error log - maybe that'll work. */
+ i_fatal_status(FATAL_LOGWRITE, "write() failed to %s log: %m",
+ log_type);
+ break;
+ default:
+ failure_exit(FATAL_LOGWRITE);
+ break;
+ }
+}
+
+static void default_post_handler(const struct failure_context *ctx)
+{
+ if (ctx->type == LOG_TYPE_ERROR && coredump_on_error)
+ abort();
+}
+
+static string_t * ATTR_FORMAT(3, 0) syslog_format(const struct failure_context *ctx,
+ size_t *prefix_len_r ATTR_UNUSED,
+ const char *format,
+ va_list args)
+{
+ string_t *str = t_str_new(128);
+ if (ctx->type == LOG_TYPE_INFO) {
+ if (ctx->log_prefix != NULL)
+ str_append(str, ctx->log_prefix);
+ else if (log_prefix != NULL)
+ str_append(str, log_prefix);
+ } else {
+ log_prefix_add(ctx, str);
+ }
+ str_vprintfa(str, format, args);
+ return str;
+}
+
+static int syslog_write(enum log_type type, string_t *data, size_t prefix_len ATTR_UNUSED)
+{
+ int level = LOG_ERR;
+
+ switch (type) {
+ case LOG_TYPE_DEBUG:
+ level = LOG_DEBUG;
+ break;
+ case LOG_TYPE_INFO:
+ level = LOG_INFO;
+ break;
+ case LOG_TYPE_WARNING:
+ level = LOG_WARNING;
+ break;
+ case LOG_TYPE_ERROR:
+ level = LOG_ERR;
+ break;
+ case LOG_TYPE_FATAL:
+ case LOG_TYPE_PANIC:
+ level = LOG_CRIT;
+ break;
+ case LOG_TYPE_COUNT:
+ case LOG_TYPE_OPTION:
+ i_unreached();
+ }
+ syslog(level, "%s", str_c(data));
+ return 0;
+}
+
+static void syslog_on_handler_failure(const struct failure_context *ctx ATTR_UNUSED)
+{
+ failure_exit(FATAL_LOGERROR);
+}
+
+static void syslog_post_handler(const struct failure_context *ctx ATTR_UNUSED)
+{
+}
+
+static string_t * ATTR_FORMAT(3, 0) internal_format(const struct failure_context *ctx,
+ size_t *prefix_len_r,
+ const char *format,
+ va_list args)
+{
+ string_t *str;
+ unsigned char log_type = ctx->type + 1;
+
+ if (ctx->log_prefix != NULL) {
+ log_type |= LOG_TYPE_FLAG_DISABLE_LOG_PREFIX;
+ if (ctx->log_prefix_type_pos != 0)
+ log_type |= LOG_TYPE_FLAG_PREFIX_LEN;
+ } else if (!log_prefix_sent && log_prefix != NULL) {
+ if (i_failure_send_option_forced("prefix", log_prefix) < 0) {
+ /* Failed to write log prefix. The log message writing
+ would likely fail as well, but don't even try since
+ the log prefix would be wrong. */
+ return NULL;
+ }
+ log_prefix_sent = TRUE;
+ }
+
+ str = t_str_new(128);
+ str_printfa(str, "\001%c%s ", log_type, my_pid);
+ if ((log_type & LOG_TYPE_FLAG_PREFIX_LEN) != 0)
+ str_printfa(str, "%u ", ctx->log_prefix_type_pos);
+ if (ctx->log_prefix != NULL)
+ str_append(str, ctx->log_prefix);
+ *prefix_len_r = str_len(str);
+
+ str_vprintfa(str, format, args);
+ return str;
+}
+
+static int internal_write(enum log_type type ATTR_UNUSED, string_t *data, size_t prefix_len)
+{
+ if (str_len(data)+1 <= PIPE_BUF) {
+ str_append_c(data, '\n');
+ return log_fd_write(STDERR_FILENO,
+ str_data(data), str_len(data));
+ }
+ return internal_send_split(data, prefix_len);
+}
+
+static void internal_on_handler_failure(const struct failure_context *ctx ATTR_UNUSED)
+{
+ failure_exit(FATAL_LOGERROR);
+}
+
+static void internal_post_handler(const struct failure_context *ctx ATTR_UNUSED)
+{
+}
+
+static struct failure_handler_vfuncs default_handler_vfuncs = {
+ .write = &default_write,
+ .format = &default_format,
+ .on_handler_failure = &default_on_handler_failure,
+ .post_handler = &default_post_handler
+};
+
+static struct failure_handler_vfuncs syslog_handler_vfuncs = {
+ .write = &syslog_write,
+ .format = &syslog_format,
+ .on_handler_failure = &syslog_on_handler_failure,
+ .post_handler = &syslog_post_handler
+};
+
+static struct failure_handler_vfuncs internal_handler_vfuncs = {
+ .write = &internal_write,
+ .format = &internal_format,
+ .on_handler_failure = &internal_on_handler_failure,
+ .post_handler = &internal_post_handler
+};
+
+struct failure_handler_config failure_handler = { .fatal_err_reset = FATAL_LOGWRITE,
+ .v = &default_handler_vfuncs };
+
+static int common_handler(const struct failure_context *ctx,
+ const char *format, va_list args)
+{
+ static int recursed = 0;
+ int ret;
+ size_t prefix_len = 0;
+
+ if (recursed >= 2) {
+ /* we're being called from some signal handler or we ran
+ out of memory */
+ return -1;
+ }
+ recursed++;
+
+ T_BEGIN {
+ string_t *str = failure_handler.v->format(ctx, &prefix_len, format, args);
+ ret = str == NULL ? -1 :
+ failure_handler.v->write(ctx->type, str, prefix_len);
+ } T_END;
+
+ if (ret < 0 && failure_ignore_errors)
+ ret = 0;
+
+ recursed--;
+ return ret;
+}
+
+static void error_handler_real(const struct failure_context *ctx,
+ const char *format, va_list args)
+{
+ if (common_handler(ctx, format, args) < 0)
+ failure_handler.v->on_handler_failure(ctx);
+ failure_handler.v->post_handler(ctx);
+}
+
+static void ATTR_FORMAT(2, 0)
+i_internal_error_handler(const struct failure_context *ctx,
+ const char *format, va_list args);
+
+/* kludgy .. we want to trust log_stamp_format with -Wformat-nonliteral */
+static const char *
+get_log_stamp_format(const char *format_arg, unsigned int timestamp_usecs)
+ ATTR_FORMAT_ARG(1);
+
+static const char *get_log_stamp_format(const char *format_arg ATTR_UNUSED,
+ unsigned int timestamp_usecs)
+{
+ if (log_stamp_format_suffix == NULL)
+ return log_stamp_format;
+ return t_strdup_printf("%s%06u%s", log_stamp_format,
+ timestamp_usecs, log_stamp_format_suffix);
+}
+
+void failure_exit(int status)
+{
+ static bool recursed = FALSE;
+
+ if (failure_exit_callback != NULL && !recursed) {
+ recursed = TRUE;
+ failure_exit_callback(&status);
+ }
+ lib_exit(status);
+}
+
+static void log_timestamp_add(const struct failure_context *ctx, string_t *str)
+{
+ const struct tm *tm = ctx->timestamp;
+ char buf[256];
+ struct timeval now;
+
+ if (log_stamp_format != NULL) {
+ if (tm == NULL) {
+ i_gettimeofday(&now);
+ tm = localtime(&now.tv_sec);
+ } else {
+ now.tv_usec = ctx->timestamp_usecs;
+ }
+
+ if (strftime(buf, sizeof(buf),
+ get_log_stamp_format("unused", now.tv_usec), tm) > 0)
+ str_append(str, buf);
+ }
+}
+
+static void log_prefix_add(const struct failure_context *ctx, string_t *str)
+{
+ if (ctx->log_prefix == NULL) {
+ /* use global log prefix */
+ if (log_prefix != NULL)
+ str_append(str, log_prefix);
+ str_append(str, failure_log_type_prefixes[ctx->type]);
+ } else if (ctx->log_prefix_type_pos == 0) {
+ str_append(str, ctx->log_prefix);
+ str_append(str, failure_log_type_prefixes[ctx->type]);
+ } else {
+ i_assert(ctx->log_prefix_type_pos <= strlen(ctx->log_prefix));
+ str_append_data(str, ctx->log_prefix, ctx->log_prefix_type_pos);
+ str_append(str, failure_log_type_prefixes[ctx->type]);
+ str_append(str, ctx->log_prefix + ctx->log_prefix_type_pos);
+ }
+}
+
+static void fd_wait_writable(int fd)
+{
+ struct pollfd pfd = {
+ .fd = fd,
+ .events = POLLOUT | POLLERR | POLLHUP | POLLNVAL,
+ };
+
+ /* Use poll() instead of ioloop, because we don't want to recurse back
+ to log writing in case something fails. */
+ if (poll(&pfd, 1, -1) < 0 && errno != EINTR) {
+ /* Unexpected error. We're already blocking on log writes,
+ so we can't log it. */
+ abort();
+ }
+}
+
+static int log_fd_write(int fd, const unsigned char *data, size_t len)
+{
+ ssize_t ret;
+ unsigned int prev_signal_term_counter = signal_term_counter;
+ unsigned int terminal_eintr_count = 0;
+ const char *old_title = NULL;
+ bool failed = FALSE, process_title_changed = FALSE;
+
+ while (!failed &&
+ (ret = write(fd, data, len)) != (ssize_t)len) {
+ if (ret > 0) {
+ /* some was written, continue.. */
+ data += ret;
+ len -= ret;
+ continue;
+ }
+ if (ret == 0) {
+ /* out of disk space? */
+ errno = ENOSPC;
+ failed = TRUE;
+ break;
+ }
+ switch (errno) {
+ case EAGAIN: {
+ /* Log fd is nonblocking - wait until we can write more.
+ Indicate in process title that the process is waiting
+ because it's waiting on the log.
+
+ Remember that the log fd is shared across processes,
+ which also means the log fd flags are shared. So if
+ one process changes the O_NONBLOCK flag for a log fd,
+ all the processes see the change. To avoid problems,
+ we'll wait using poll() instead of changing the
+ O_NONBLOCK flag. */
+ if (!process_title_changed) {
+ const char *title;
+
+ process_title_changed = TRUE;
+ old_title = t_strdup(process_title_get());
+ if (old_title == NULL)
+ title = "[blocking on log write]";
+ else
+ title = t_strdup_printf("%s - [blocking on log write]",
+ old_title);
+ process_title_set(title);
+ }
+ fd_wait_writable(fd);
+ break;
+ }
+ case EINTR:
+ if (prev_signal_term_counter == signal_term_counter) {
+ /* non-terminal signal. ignore. */
+ } else if (terminal_eintr_count++ == 0) {
+ /* we'd rather not die in the middle of
+ writing to log. try again once more */
+ } else {
+ /* received two terminal signals.
+ someone wants us dead. */
+ failed = TRUE;
+ break;
+ }
+ break;
+ default:
+ failed = TRUE;
+ break;
+ }
+ prev_signal_term_counter = signal_term_counter;
+ }
+ if (process_title_changed)
+ process_title_set(old_title);
+ return failed ? -1 : 0;
+}
+
+static void ATTR_NORETURN
+default_fatal_finish(enum log_type type, int status)
+{
+ const char *backtrace;
+ static int recursed = 0;
+
+ recursed++;
+ if ((type == LOG_TYPE_PANIC || status == FATAL_OUTOFMEM) &&
+ recursed == 1) {
+ if (backtrace_get(&backtrace) == 0)
+ i_error("Raw backtrace: %s", backtrace);
+ }
+ recursed--;
+
+ if (type == LOG_TYPE_PANIC || getenv("CORE_ERROR") != NULL ||
+ (status == FATAL_OUTOFMEM && getenv("CORE_OUTOFMEM") != NULL))
+ abort();
+ else
+ failure_exit(status);
+}
+
+static void ATTR_NORETURN fatal_handler_real(const struct failure_context *ctx,
+ const char *format, va_list args)
+{
+ int status = ctx->exit_status;
+ if (common_handler(ctx, format, args) < 0 &&
+ status == FATAL_DEFAULT)
+ status = failure_handler.fatal_err_reset;
+ default_fatal_finish(ctx->type, status);
+}
+
+void default_fatal_handler(const struct failure_context *ctx,
+ const char *format, va_list args)
+{
+ failure_handler.v = &default_handler_vfuncs;
+ failure_handler.fatal_err_reset = FATAL_LOGWRITE;
+ fatal_handler_real(ctx, format, args);
+}
+
+void default_error_handler(const struct failure_context *ctx,
+ const char *format, va_list args)
+{
+ failure_handler.v = &default_handler_vfuncs;
+ failure_handler.fatal_err_reset = FATAL_LOGWRITE;
+ error_handler_real(ctx, format, args);
+}
+
+void i_log_type(const struct failure_context *ctx, const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ i_log_typev(ctx, format, args);
+ va_end(args);
+}
+
+void i_log_typev(const struct failure_context *ctx, const char *format,
+ va_list args)
+{
+ switch (ctx->type) {
+ case LOG_TYPE_DEBUG:
+ debug_handler(ctx, format, args);
+ break;
+ case LOG_TYPE_INFO:
+ info_handler(ctx, format, args);
+ break;
+ default:
+ error_handler(ctx, format, args);
+ }
+}
+
+void i_panic(const char *format, ...)
+{
+ struct failure_context ctx;
+ va_list args;
+
+ lib_set_clean_exit(TRUE);
+ i_zero(&ctx);
+ ctx.type = LOG_TYPE_PANIC;
+
+ va_start(args, format);
+ fatal_handler(&ctx, format, args);
+ i_unreached();
+ /*va_end(args);*/
+}
+
+void i_fatal(const char *format, ...)
+{
+ struct failure_context ctx;
+ va_list args;
+
+ lib_set_clean_exit(TRUE);
+ i_zero(&ctx);
+ ctx.type = LOG_TYPE_FATAL;
+ ctx.exit_status = FATAL_DEFAULT;
+
+ va_start(args, format);
+ fatal_handler(&ctx, format, args);
+ i_unreached();
+ /*va_end(args);*/
+}
+
+void i_fatal_status(int status, const char *format, ...)
+{
+ struct failure_context ctx;
+ va_list args;
+
+ lib_set_clean_exit(TRUE);
+ i_zero(&ctx);
+ ctx.type = LOG_TYPE_FATAL;
+ ctx.exit_status = status;
+
+ va_start(args, format);
+ fatal_handler(&ctx, format, args);
+ i_unreached();
+ /*va_end(args);*/
+}
+
+void i_error(const char *format, ...)
+{
+ int old_errno = errno;
+ va_list args;
+
+ va_start(args, format);
+ error_handler(&failure_ctx_error, format, args);
+ va_end(args);
+
+ errno = old_errno;
+}
+
+void i_warning(const char *format, ...)
+{
+ int old_errno = errno;
+ va_list args;
+
+ va_start(args, format);
+ error_handler(&failure_ctx_warning, format, args);
+ va_end(args);
+
+ errno = old_errno;
+}
+
+void i_info(const char *format, ...)
+{
+ int old_errno = errno;
+ va_list args;
+
+ va_start(args, format);
+ info_handler(&failure_ctx_info, format, args);
+ va_end(args);
+
+ errno = old_errno;
+}
+
+void i_debug(const char *format, ...)
+{
+ int old_errno = errno;
+ va_list args;
+
+ va_start(args, format);
+ debug_handler(&failure_ctx_debug, format, args);
+ va_end(args);
+
+ errno = old_errno;
+}
+
+void i_set_fatal_handler(failure_callback_t *callback ATTR_NORETURN)
+{
+ fatal_handler = callback;
+}
+
+void i_set_error_handler(failure_callback_t *callback)
+{
+ coredump_on_error = getenv("CORE_ERROR") != NULL;
+ error_handler = callback;
+}
+
+void i_set_info_handler(failure_callback_t *callback)
+{
+ info_handler = callback;
+}
+
+void i_set_debug_handler(failure_callback_t *callback)
+{
+ debug_handler = callback;
+}
+
+void i_get_failure_handlers(failure_callback_t **fatal_callback_r,
+ failure_callback_t **error_callback_r,
+ failure_callback_t **info_callback_r,
+ failure_callback_t **debug_callback_r)
+{
+ *fatal_callback_r = fatal_handler;
+ *error_callback_r = error_handler;
+ *info_callback_r = info_handler;
+ *debug_callback_r = debug_handler;
+}
+
+void i_syslog_fatal_handler(const struct failure_context *ctx,
+ const char *format, va_list args)
+{
+ failure_handler.v = &syslog_handler_vfuncs;
+ failure_handler.fatal_err_reset = FATAL_LOGERROR;
+ fatal_handler_real(ctx, format, args);
+}
+
+void i_syslog_error_handler(const struct failure_context *ctx,
+ const char *format, va_list args)
+{
+ failure_handler.v = &syslog_handler_vfuncs;
+ failure_handler.fatal_err_reset = FATAL_LOGERROR;
+ error_handler_real(ctx, format, args);
+}
+
+void i_set_failure_syslog(const char *ident, int options, int facility)
+{
+ openlog(ident, options, facility);
+
+ i_set_fatal_handler(i_syslog_fatal_handler);
+ i_set_error_handler(i_syslog_error_handler);
+ i_set_info_handler(i_syslog_error_handler);
+ i_set_debug_handler(i_syslog_error_handler);
+}
+
+static void open_log_file(int *fd, const char *path)
+{
+ const char *str;
+
+ if (*fd != STDERR_FILENO) {
+ if (close(*fd) < 0) {
+ str = t_strdup_printf("close(%d) failed: %m\n", *fd);
+ (void)write_full(STDERR_FILENO, str, strlen(str));
+ }
+ }
+
+ if (path == NULL || strcmp(path, "/dev/stderr") == 0)
+ *fd = STDERR_FILENO;
+ else {
+ *fd = open(path, O_CREAT | O_APPEND | O_WRONLY, 0600);
+ if (*fd == -1) {
+ *fd = STDERR_FILENO;
+ str = t_strdup_printf("Can't open log file %s: %m\n",
+ path);
+ (void)write_full(STDERR_FILENO, str, strlen(str));
+ if (fd == &log_fd)
+ failure_exit(FATAL_LOGOPEN);
+ else
+ i_fatal_status(FATAL_LOGOPEN, "%s", str);
+ }
+ fd_close_on_exec(*fd, TRUE);
+ }
+}
+
+void i_set_failure_file(const char *path, const char *prefix)
+{
+ i_set_failure_prefix("%s", prefix);
+
+ if (log_info_fd != STDERR_FILENO && log_info_fd != log_fd) {
+ if (close(log_info_fd) < 0)
+ i_error("close(%d) failed: %m", log_info_fd);
+ }
+
+ if (log_debug_fd != STDERR_FILENO && log_debug_fd != log_info_fd &&
+ log_debug_fd != log_fd) {
+ if (close(log_debug_fd) < 0)
+ i_error("close(%d) failed: %m", log_debug_fd);
+ }
+
+ open_log_file(&log_fd, path);
+ /* if info/debug logs are elsewhere, i_set_info/debug_file()
+ overrides these later. */
+ log_info_fd = log_fd;
+ log_debug_fd = log_fd;
+
+ i_set_fatal_handler(default_fatal_handler);
+ i_set_error_handler(default_error_handler);
+ i_set_info_handler(default_error_handler);
+ i_set_debug_handler(default_error_handler);
+}
+
+static int i_failure_send_option_forced(const char *key, const char *value)
+{
+ const char *str;
+
+ str = t_strdup_printf("\001%c%s %s=%s\n", LOG_TYPE_OPTION+1,
+ my_pid, key, value);
+ return log_fd_write(STDERR_FILENO, (const unsigned char *)str,
+ strlen(str));
+}
+
+static void i_failure_send_option(const char *key, const char *value)
+{
+ if (error_handler == i_internal_error_handler)
+ (void)i_failure_send_option_forced(key, value);
+}
+
+void i_set_failure_prefix(const char *prefix_fmt, ...)
+{
+ va_list args;
+
+ va_start(args, prefix_fmt);
+ i_free(log_prefix);
+ log_prefix = i_strdup_vprintf(prefix_fmt, args);
+ va_end(args);
+
+ log_prefix_sent = FALSE;
+}
+
+void i_unset_failure_prefix(void)
+{
+ i_free(log_prefix);
+ log_prefix = i_strdup("");
+ log_prefix_sent = FALSE;
+}
+
+const char *i_get_failure_prefix(void)
+{
+ return log_prefix != NULL ? log_prefix : "";
+}
+
+static int internal_send_split(string_t *full_str, size_t prefix_len)
+{
+ /* This function splits the log line into PIPE_BUF sized blocks, so
+ the log process doesn't see partial lines. The log prefix is
+ repeated for each sent line. However, if the log prefix is
+ excessively long, we're still going to send the log lines even
+ if they are longer than PIPE_BUF. LINE_MIN_TEXT_LEN controls the
+ minimum number of bytes we're going to send of the actual log line
+ regardless of the log prefix length. (Alternative solution could be
+ to just forcibly split the line to PIPE_BUF length blocks without
+ repeating the log prefix for subsequent lines.) */
+#define LINE_MIN_TEXT_LEN 128
+#if LINE_MIN_TEXT_LEN >= PIPE_BUF
+# error LINE_MIN_TEXT_LEN too large
+#endif
+ string_t *str;
+ size_t max_text_len, pos = prefix_len;
+
+ str = t_str_new(PIPE_BUF);
+ str_append_data(str, str_data(full_str), prefix_len);
+ if (prefix_len < PIPE_BUF) {
+ max_text_len = I_MAX(PIPE_BUF - prefix_len - 1,
+ LINE_MIN_TEXT_LEN);
+ } else {
+ max_text_len = LINE_MIN_TEXT_LEN;
+ }
+
+ while (pos < str_len(full_str)) {
+ str_truncate(str, prefix_len);
+ str_append_max(str, str_c(full_str) + pos, max_text_len);
+ str_append_c(str, '\n');
+ if (log_fd_write(STDERR_FILENO,
+ str_data(str), str_len(str)) < 0)
+ return -1;
+ pos += max_text_len;
+ }
+ return 0;
+}
+
+
+static bool line_parse_prefix(const char *line, enum log_type *log_type_r,
+ bool *replace_prefix_r, bool *have_prefix_len_r)
+{
+ if (*line != 1)
+ return FALSE;
+
+ unsigned char log_type = (line[1] & 0x3f);
+ if (log_type == '\0') {
+ i_warning("Broken log line follows (type=NUL)");
+ return FALSE;
+ }
+ log_type--;
+
+ if (log_type > LOG_TYPE_OPTION) {
+ i_warning("Broken log line follows (type=%d)", log_type);
+ return FALSE;
+ }
+ *log_type_r = log_type;
+ *replace_prefix_r = (line[1] & LOG_TYPE_FLAG_DISABLE_LOG_PREFIX) != 0;
+ *have_prefix_len_r = (line[1] & LOG_TYPE_FLAG_PREFIX_LEN) != 0;
+ return TRUE;
+}
+
+void i_failure_parse_line(const char *line, struct failure_line *failure)
+{
+ bool have_prefix_len = FALSE;
+
+ i_zero(failure);
+ if (!line_parse_prefix(line, &failure->log_type,
+ &failure->disable_log_prefix,
+ &have_prefix_len)) {
+ failure->log_type = LOG_TYPE_ERROR;
+ failure->text = line;
+ return;
+ }
+
+ line += 2;
+ failure->text = line;
+ while (*line >= '0' && *line <= '9') {
+ failure->pid = failure->pid*10 + (*line - '0');
+ line++;
+ }
+ if (*line != ' ') {
+ /* some old protocol? */
+ failure->pid = 0;
+ return;
+ }
+ line++;
+
+ if (have_prefix_len) {
+ if (str_parse_uint(line, &failure->log_prefix_len, &line) < 0 ||
+ line[0] != ' ') {
+ /* unexpected, but ignore */
+ } else {
+ line++;
+ if (failure->log_prefix_len > strlen(line)) {
+ /* invalid */
+ failure->log_prefix_len = 0;
+ }
+ }
+ }
+ failure->text = line;
+}
+
+static void ATTR_NORETURN ATTR_FORMAT(2, 0)
+i_internal_fatal_handler(const struct failure_context *ctx,
+ const char *format, va_list args)
+{
+ failure_handler.v = &internal_handler_vfuncs;
+ failure_handler.fatal_err_reset = FATAL_LOGERROR;
+ fatal_handler_real(ctx, format, args);
+
+
+}
+
+static void
+i_internal_error_handler(const struct failure_context *ctx,
+ const char *format, va_list args)
+{
+ failure_handler.v = &internal_handler_vfuncs;
+ failure_handler.fatal_err_reset = FATAL_LOGERROR;
+ error_handler_real(ctx, format, args);
+}
+
+void i_set_failure_internal(void)
+{
+ fd_set_nonblock(STDERR_FILENO, TRUE);
+ i_set_fatal_handler(i_internal_fatal_handler);
+ i_set_error_handler(i_internal_error_handler);
+ i_set_info_handler(i_internal_error_handler);
+ i_set_debug_handler(i_internal_error_handler);
+}
+
+bool i_failure_handler_is_internal(failure_callback_t *const callback)
+{
+ return callback == i_internal_fatal_handler ||
+ callback == i_internal_error_handler;
+}
+
+void i_set_failure_ignore_errors(bool ignore)
+{
+ failure_ignore_errors = ignore;
+}
+
+void i_set_info_file(const char *path)
+{
+ if (log_info_fd == log_fd)
+ log_info_fd = STDERR_FILENO;
+
+ open_log_file(&log_info_fd, path);
+ info_handler = default_error_handler;
+ /* write debug-level messages to the info_log_path,
+ until i_set_debug_file() was called */
+ log_debug_fd = log_info_fd;
+ i_set_debug_handler(default_error_handler);
+}
+
+void i_set_debug_file(const char *path)
+{
+ if (log_debug_fd == log_fd || log_debug_fd == log_info_fd)
+ log_debug_fd = STDERR_FILENO;
+
+ open_log_file(&log_debug_fd, path);
+ debug_handler = default_error_handler;
+}
+
+void i_set_failure_timestamp_format(const char *fmt)
+{
+ const char *p;
+
+ i_free(log_stamp_format);
+ i_free_and_null(log_stamp_format_suffix);
+
+ p = strstr(fmt, "%{usecs}");
+ if (p == NULL)
+ log_stamp_format = i_strdup(fmt);
+ else {
+ log_stamp_format = i_strdup_until(fmt, p);
+ log_stamp_format_suffix = i_strdup(p + 8);
+ }
+}
+
+void i_set_failure_send_ip(const struct ip_addr *ip)
+{
+ i_failure_send_option("ip", net_ip2addr(ip));
+}
+
+void i_set_failure_send_prefix(const char *prefix)
+{
+ i_failure_send_option("prefix", prefix);
+}
+
+void i_set_failure_exit_callback(void (*callback)(int *status))
+{
+ failure_exit_callback = callback;
+}
+
+void failures_deinit(void)
+{
+ if (log_debug_fd == log_info_fd || log_debug_fd == log_fd)
+ log_debug_fd = STDERR_FILENO;
+
+ if (log_info_fd == log_fd)
+ log_info_fd = STDERR_FILENO;
+
+ if (log_fd != STDERR_FILENO) {
+ i_close_fd(&log_fd);
+ log_fd = STDERR_FILENO;
+ }
+
+ if (log_info_fd != STDERR_FILENO) {
+ i_close_fd(&log_info_fd);
+ log_info_fd = STDERR_FILENO;
+ }
+
+ if (log_debug_fd != STDERR_FILENO) {
+ i_close_fd(&log_debug_fd);
+ log_debug_fd = STDERR_FILENO;
+ }
+
+ i_free_and_null(log_prefix);
+ i_free_and_null(log_stamp_format);
+ i_free_and_null(log_stamp_format_suffix);
+}
+
+#undef i_unreached
+void i_unreached(const char *source_filename, int source_linenum)
+{
+ i_panic("file %s: line %d: unreached", source_filename, source_linenum);
+}