failures.c revision 04f0944b16374064e8d657f3925082a2613da4b9
bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen#include "lib.h"
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen#include "ioloop.h"
5e327e031d1591f8bff17b67eba7139afbd36cddTimo Sirainen#include "str.h"
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen#include "backtrace-string.h"
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen#include "printf-format-fix.h"
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen#include "write-full.h"
a2fdfd2efdbb2d912aad23900a466cf74114920bTimo Sirainen#include "fd-close-on-exec.h"
d29c67900d3d104e24ed213ed4a10d204decdf18Aki Tuomi
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen#include <stdlib.h>
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi#include <unistd.h>
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen#include <fcntl.h>
c6be98b5270900746f35ebe28bd636019976e29eTimo Sirainen#include <syslog.h>
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen#include <time.h>
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenconst char *failure_log_type_prefixes[] = {
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi "Info: ",
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen "Warning: ",
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen "Error: ",
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen "Fatal: ",
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi "Panic: "
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen};
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenstatic char log_type_internal_chars[] = {
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen 'I', 'W', 'E', 'F', 'P'
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen};
5e327e031d1591f8bff17b67eba7139afbd36cddTimo Sirainen
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen/* Initialize working defaults */
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenstatic fatal_failure_callback_t *fatal_handler ATTR_NORETURN =
f36c4185474823594a78b3f252e79d8923522c36Timo Sirainen default_fatal_handler;
f36c4185474823594a78b3f252e79d8923522c36Timo Sirainenstatic failure_callback_t *error_handler = default_error_handler;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenstatic failure_callback_t *info_handler = default_error_handler;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenstatic void (*failure_exit_callback)(int *) = NULL;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenstatic int log_fd = STDERR_FILENO, log_info_fd = STDERR_FILENO;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenstatic char *log_prefix = NULL, *log_stamp_format = NULL;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen/* kludgy .. we want to trust log_stamp_format with -Wformat-nonliteral */
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenstatic const char *get_log_stamp_format(const char *unused)
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen ATTR_FORMAT_ARG(1);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenstatic const char *get_log_stamp_format(const char *unused ATTR_UNUSED)
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen{
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen return log_stamp_format;
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi}
f300f927771a39549ce6cb7607129508e9041b4aStephan Bosch
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomivoid failure_exit(int status)
e60c3e17c656c53da60f0ac51aa15e9ef2742d77Stephan Bosch{
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi if (failure_exit_callback != NULL)
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi failure_exit_callback(&status);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen exit(status);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen}
45ead232666a47819e89dc71dec57767340d0b62Timo Sirainen
45ead232666a47819e89dc71dec57767340d0b62Timo Sirainenstatic void log_prefix_add(string_t *str)
45ead232666a47819e89dc71dec57767340d0b62Timo Sirainen{
45ead232666a47819e89dc71dec57767340d0b62Timo Sirainen struct tm *tm;
45ead232666a47819e89dc71dec57767340d0b62Timo Sirainen char buf[256];
45ead232666a47819e89dc71dec57767340d0b62Timo Sirainen time_t now;
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi if (log_prefix != NULL)
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi str_append(str, log_prefix);
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi if (log_stamp_format != NULL) {
1e11a94ec50fc9b57eb2c859771c6a326ccaf86fAki Tuomi now = time(NULL);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen tm = localtime(&now);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen if (strftime(buf, sizeof(buf),
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen get_log_stamp_format("unused"), tm) > 0)
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen str_append(str, buf);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen }
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen}
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenstatic void log_fd_flush_stop(struct ioloop *ioloop)
f36c4185474823594a78b3f252e79d8923522c36Timo Sirainen{
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen io_loop_stop(ioloop);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen}
f36c4185474823594a78b3f252e79d8923522c36Timo Sirainen
5e327e031d1591f8bff17b67eba7139afbd36cddTimo Sirainenstatic int log_fd_write(int fd, const unsigned char *data, unsigned int len)
5e327e031d1591f8bff17b67eba7139afbd36cddTimo Sirainen{
5e327e031d1591f8bff17b67eba7139afbd36cddTimo Sirainen struct ioloop *ioloop;
5e327e031d1591f8bff17b67eba7139afbd36cddTimo Sirainen struct io *io;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen ssize_t ret;
45ead232666a47819e89dc71dec57767340d0b62Timo Sirainen
a13b1245bee0b6524b4aeb3c8fd9e34af648b746Aki Tuomi while ((ret = write(fd, data, len)) != (ssize_t)len) {
c6be98b5270900746f35ebe28bd636019976e29eTimo Sirainen if (ret > 0) {
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen /* some was written, continue.. */
80a225c0b1f4bf322a562cc7c21d5891fb6895eeStephan Bosch data += ret;
a2fdfd2efdbb2d912aad23900a466cf74114920bTimo Sirainen len -= ret;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen continue;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen }
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen if (ret == 0) {
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen /* out of disk space? */
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen return -1;
80a225c0b1f4bf322a562cc7c21d5891fb6895eeStephan Bosch }
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen if (errno != EAGAIN)
c6be98b5270900746f35ebe28bd636019976e29eTimo Sirainen return -1;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
a2fdfd2efdbb2d912aad23900a466cf74114920bTimo Sirainen /* wait until we can write more. this can happen at least
237a6211c7fc4d6dbb58dd0467da6dba1b8f21f6Timo Sirainen when writing to terminal, even if fd is blocking. */
45ead232666a47819e89dc71dec57767340d0b62Timo Sirainen ioloop = io_loop_create();
f36c4185474823594a78b3f252e79d8923522c36Timo Sirainen io = io_add(IO_WRITE, fd, log_fd_flush_stop, ioloop);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen io_loop_run(ioloop);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen io_remove(&io);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen io_loop_destroy(&ioloop);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen }
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen return 0;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen}
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenstatic int ATTR_FORMAT(3, 0)
2d1892aaeb63b9774237b6e60d6bb04bf6f8259cTimo Sirainendefault_handler(const char *prefix, int fd, const char *format, va_list args)
838d6a4751c3fbe17c3ec45c0e109629c4156815Timo Sirainen{
2d1892aaeb63b9774237b6e60d6bb04bf6f8259cTimo Sirainen static int recursed = 0;
c1fc5a97a15332f1253ee13a9cab65a7b4b6cd5fTimo Sirainen int ret, old_errno = errno;
c1fc5a97a15332f1253ee13a9cab65a7b4b6cd5fTimo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen if (recursed >= 2) {
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainen /* we're being called from some signal handler or we ran
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen out of memory */
2d1892aaeb63b9774237b6e60d6bb04bf6f8259cTimo Sirainen return 0;
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainen }
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainen
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainen recursed++;
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainen
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainen T_BEGIN {
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainen string_t *str = t_str_new(256);
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainen log_prefix_add(str);
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainen str_append(str, prefix);
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainen
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainen /* make sure there's no %n in there and fix %m */
107659c01b2359b0ee426bde020c8d4e29ede30dTimo Sirainen str_vprintfa(str, printf_format_fix(format), args);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen str_append_c(str, '\n');
efe78d3ba24fc866af1c79b9223dc0809ba26cadStephan Bosch
c1fc5a97a15332f1253ee13a9cab65a7b4b6cd5fTimo Sirainen ret = log_fd_write(fd, str_data(str), str_len(str));
c1fc5a97a15332f1253ee13a9cab65a7b4b6cd5fTimo Sirainen } T_END;
c1fc5a97a15332f1253ee13a9cab65a7b4b6cd5fTimo Sirainen
c1fc5a97a15332f1253ee13a9cab65a7b4b6cd5fTimo Sirainen errno = old_errno;
c1fc5a97a15332f1253ee13a9cab65a7b4b6cd5fTimo Sirainen recursed--;
c1fc5a97a15332f1253ee13a9cab65a7b4b6cd5fTimo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen return ret;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen}
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainenvoid default_fatal_handler(enum log_type type, int status,
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen const char *format, va_list args)
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen{
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen const char *backtrace;
d6b3cfd855c0eebed68be50d3111de1b5a6afeb0Timo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen if (default_handler(failure_log_type_prefixes[type], log_fd, format,
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen args) < 0 && status == FATAL_DEFAULT)
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen status = FATAL_LOGWRITE;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen if (type == LOG_TYPE_PANIC) {
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen if (backtrace_get(&backtrace) == 0)
i_error("Raw backtrace: %s", backtrace);
}
if (type == LOG_TYPE_PANIC)
abort();
else
failure_exit(status);
}
void default_error_handler(enum log_type type, const char *format, va_list args)
{
int fd = type == LOG_TYPE_INFO ? log_info_fd : log_fd;
if (default_handler(failure_log_type_prefixes[type],
fd, format, args) < 0)
failure_exit(FATAL_LOGWRITE);
}
void i_log_type(enum log_type type, const char *format, ...)
{
va_list args;
va_start(args, format);
if (type == LOG_TYPE_INFO)
info_handler(type, format, args);
else
error_handler(type, format, args);
va_end(args);
}
void i_panic(const char *format, ...)
{
va_list args;
va_start(args, format);
fatal_handler(LOG_TYPE_PANIC, 0, format, args);
va_end(args);
}
void i_fatal(const char *format, ...)
{
va_list args;
va_start(args, format);
fatal_handler(LOG_TYPE_FATAL, FATAL_DEFAULT, format, args);
va_end(args);
}
void i_fatal_status(int status, const char *format, ...)
{
va_list args;
va_start(args, format);
fatal_handler(LOG_TYPE_FATAL, status, format, args);
va_end(args);
}
void i_error(const char *format, ...)
{
int old_errno = errno;
va_list args;
va_start(args, format);
error_handler(LOG_TYPE_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(LOG_TYPE_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(LOG_TYPE_INFO, format, args);
va_end(args);
errno = old_errno;
}
void i_set_fatal_handler(fatal_failure_callback_t *callback ATTR_NORETURN)
{
if (callback == NULL)
callback = default_fatal_handler;
fatal_handler = callback;
}
void i_set_error_handler(failure_callback_t *callback)
{
if (callback == NULL)
callback = default_error_handler;
error_handler = callback;
}
void i_set_info_handler(failure_callback_t *callback)
{
if (callback == NULL)
callback = default_error_handler;
info_handler = callback;
}
static int ATTR_FORMAT(2, 0)
syslog_handler(int level, const char *format, va_list args)
{
static int recursed = 0;
if (recursed != 0)
return -1;
recursed++;
/* make sure there's no %n in there. vsyslog() supports %m, but since
we'll convert it ourself anyway, we might as well it */
vsyslog(level, printf_format_fix_unsafe(format), args);
recursed--;
return 0;
}
void i_syslog_fatal_handler(enum log_type type, int status,
const char *fmt, va_list args)
{
const char *backtrace;
if (syslog_handler(LOG_CRIT, fmt, args) < 0 && status == FATAL_DEFAULT)
status = FATAL_LOGERROR;
if (type == LOG_TYPE_PANIC) {
if (backtrace_get(&backtrace) == 0)
i_error("Raw backtrace: %s", backtrace);
abort();
} else {
failure_exit(status);
}
}
void i_syslog_error_handler(enum log_type type, const char *fmt, va_list args)
{
int level = LOG_ERR;
switch (type) {
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;
}
if (syslog_handler(level, fmt, args) < 0)
failure_exit(FATAL_LOGERROR);
}
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);
}
static void open_log_file(int *fd, const char *path)
{
char buf[PATH_MAX];
if (*fd != STDERR_FILENO) {
if (close(*fd) < 0) {
i_snprintf(buf, sizeof(buf),
"close(%d) failed: %m", *fd);
(void)write_full(STDERR_FILENO, buf, strlen(buf));
}
}
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;
i_snprintf(buf, sizeof(buf),
"Can't open log file %s: %m", path);
(void)write_full(STDERR_FILENO, buf, strlen(buf));
failure_exit(FATAL_LOGOPEN);
}
fd_close_on_exec(*fd, TRUE);
}
}
void i_set_failure_file(const char *path, const char *prefix)
{
i_set_failure_prefix(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);
}
open_log_file(&log_fd, path);
log_info_fd = log_fd;
i_set_fatal_handler(NULL);
i_set_error_handler(NULL);
i_set_info_handler(NULL);
}
void i_set_failure_prefix(const char *prefix)
{
i_free(log_prefix);
log_prefix = i_strdup(prefix);
}
static int ATTR_FORMAT(2, 0)
internal_handler(char log_type, const char *format, va_list args)
{
int ret;
T_BEGIN {
string_t *str;
str = t_str_new(512);
str_append_c(str, 1);
str_append_c(str, log_type);
str_vprintfa(str, format, args);
str_append_c(str, '\n');
ret = write_full(2, str_data(str), str_len(str));
} T_END;
return ret;
}
static void ATTR_NORETURN ATTR_FORMAT(3, 0)
i_internal_fatal_handler(enum log_type type, int status,
const char *fmt, va_list args)
{
const char *backtrace;
if (internal_handler(log_type_internal_chars[type], fmt, args) < 0 &&
status == FATAL_DEFAULT)
status = FATAL_LOGERROR;
if (type == LOG_TYPE_PANIC) {
if (backtrace_get(&backtrace) == 0)
i_error("Raw backtrace: %s", backtrace);
abort();
} else {
failure_exit(status);
}
}
static void ATTR_FORMAT(2, 0)
i_internal_error_handler(enum log_type type, const char *fmt, va_list args)
{
if (internal_handler(log_type_internal_chars[type], fmt, args) < 0)
failure_exit(FATAL_LOGERROR);
}
void i_set_failure_internal(void)
{
i_set_fatal_handler(i_internal_fatal_handler);
i_set_error_handler(i_internal_error_handler);
i_set_info_handler(i_internal_error_handler);
}
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;
}
void i_set_failure_timestamp_format(const char *fmt)
{
i_free(log_stamp_format);
log_stamp_format = i_strdup(fmt);
}
void i_set_failure_exit_callback(void (*callback)(int *status))
{
failure_exit_callback = callback;
}
void failures_deinit(void)
{
if (log_info_fd == log_fd)
log_info_fd = STDERR_FILENO;
if (log_fd != STDERR_FILENO) {
(void)close(log_fd);
log_fd = STDERR_FILENO;
}
if (log_info_fd != STDERR_FILENO) {
(void)close(log_info_fd);
log_info_fd = STDERR_FILENO;
}
i_free_and_null(log_prefix);
i_free_and_null(log_stamp_format);
}