diff options
Diffstat (limited to 'src/lib/failures.c')
-rw-r--r-- | src/lib/failures.c | 998 |
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); +} |