/* $Id: messages.c 5496 2002-06-07 13:59:06Z alexk $ ** ** Message and error reporting (possibly fatal). ** ** Usage: ** ** extern int cleanup(void); ** extern void log(int, const char *, va_list, int); ** ** message_fatal_cleanup = cleanup; ** message_program_name = argv[0]; ** ** warn("Something horrible happened at %lu", time); ** syswarn("Couldn't unlink temporary file %s", tmpfile); ** ** die("Something fatal happened at %lu", time); ** sysdie("open of %s failed", filename); ** ** debug("Some debugging message about %s", string); ** trace(TRACE_PROGRAM, "Program trace output"); ** notice("Informational notices"); ** ** message_handlers_warn(1, log); ** warn("This now goes through our log function"); ** ** These functions implement message reporting through user-configurable ** handler functions. debug() only does something if DEBUG is defined, ** trace() supports sending trace messages in one of a number of configurable ** classes of traces so that they can be turned on or off independently, and ** notice() and warn() just output messages as configured. die() similarly ** outputs a message but then exits, normally with a status of 1. ** ** The sys* versions do the same, but append a colon, a space, and the ** results of strerror(errno) to the end of the message. All functions ** accept printf-style formatting strings and arguments. ** ** If message_fatal_cleanup is non-NULL, it is called before exit by die and ** sysdie and its return value is used as the argument to exit. It is a ** pointer to a function taking no arguments and returning an int, and can be ** used to call cleanup functions or to exit in some alternate fashion (such ** as by calling _exit). ** ** If message_program_name is non-NULL, the string it points to, followed by ** a colon and a space, is prepended to all error messages logged through the ** message_log_stdout and message_log_stderr message handlers (the former is ** the default for notice, and the latter is the default for warn and die). ** ** Honoring error_program_name and printing to stderr is just the default ** handler; with message_handlers_* the handlers for any message function can ** be changed. By default, notice prints to stdout, warn and die print to ** stderr, and the others don't do anything at all. These functions take a ** count of handlers and then that many function pointers, each one to a ** function that takes a message length (the number of characters snprintf ** generates given the format and arguments), a format, an argument list as a ** va_list, and the applicable errno value (if any). */ /* Used for unused parameters to silence gcc warnings. */ #define UNUSED __attribute__((__unused__)) /* Make available the bool type. */ #if INN_HAVE_STDBOOL_H # include #else # undef true # undef false # define true (1) # define false (0) # ifndef __cplusplus # define bool int # endif #endif /* INN_HAVE_STDBOOL_H */ #include #include #include #include #include #include #include #include #include #include #include "xmalloc.h" /* These are the currently-supported types of traces. */ enum message_trace { TRACE_NETWORK, /* Network traffic. */ TRACE_PROGRAM, /* Stages of program execution. */ TRACE_ALL /* All traces; this must be last. */ }; /* The reporting functions. The ones prefaced by "sys" add a colon, a space, and the results of strerror(errno) to the output and are intended for reporting failures of system calls. */ extern void trace(enum message_trace, const char *, ...) __attribute__((__format__(printf, 2, 3))); extern void notice(const char *, ...) __attribute__((__format__(printf, 1, 2))); extern void sysnotice(const char *, ...) __attribute__((__format__(printf, 1, 2))); extern void warn(const char *, ...) __attribute__((__format__(printf, 1, 2))); extern void syswarn(const char *, ...) __attribute__((__format__(printf, 1, 2))); extern void die(const char *, ...) __attribute__((__noreturn__, __format__(printf, 1, 2))); extern void sysdie(const char *, ...) __attribute__((__noreturn__, __format__(printf, 1, 2))); /* Debug is handled specially, since we want to make the code disappear completely unless we're built with -DDEBUG. We can only do that with support for variadic macros, though; otherwise, the function just won't do anything. */ #if !defined(DEBUG) && (INN_HAVE_C99_VAMACROS || INN_HAVE_GNU_VAMACROS) # if INN_HAVE_C99_VAMACROS # define debug(format, ...) /* empty */ # elif INN_HAVE_GNU_VAMACROS # define debug(format, args...) /* empty */ # endif #else extern void debug(const char *, ...) __attribute__((__format__(printf, 1, 2))); #endif /* Set the handlers for various message functions. All of these functions take a count of the number of handlers and then function pointers for each of those handlers. These functions are not thread-safe; they set global variables. */ extern void message_handlers_debug(int count, ...); extern void message_handlers_trace(int count, ...); extern void message_handlers_notice(int count, ...); extern void message_handlers_warn(int count, ...); extern void message_handlers_die(int count, ...); /* Enable or disable tracing for particular classes of messages. */ extern void message_trace_enable(enum message_trace, bool); /* Some useful handlers, intended to be passed to message_handlers_*. All handlers take the length of the formatted message, the format, a variadic argument list, and the errno setting if any. */ extern void message_log_stdout(int, const char *, va_list, int); extern void message_log_stderr(int, const char *, va_list, int); extern void message_log_syslog_debug(int, const char *, va_list, int); extern void message_log_syslog_info(int, const char *, va_list, int); extern void message_log_syslog_notice(int, const char *, va_list, int); extern void message_log_syslog_warning(int, const char *, va_list, int); extern void message_log_syslog_err(int, const char *, va_list, int); extern void message_log_syslog_crit(int, const char *, va_list, int); /* The type of a message handler. */ typedef void (*message_handler_func)(int, const char *, va_list, int); /* If non-NULL, called before exit and its return value passed to exit. */ extern int (*message_fatal_cleanup)(void); /* If non-NULL, prepended (followed by ": ") to all messages printed by either message_log_stdout or message_log_stderr. */ extern const char *message_program_name; /* The default handler lists. */ static message_handler_func stdout_handlers[2] = { message_log_stdout, NULL }; static message_handler_func stderr_handlers[2] = { message_log_stderr, NULL }; /* The list of logging functions currently in effect. */ static message_handler_func *debug_handlers = NULL; static message_handler_func *trace_handlers = NULL; static message_handler_func *notice_handlers = stdout_handlers; static message_handler_func *warn_handlers = stderr_handlers; static message_handler_func *die_handlers = stderr_handlers; /* If non-NULL, called before exit and its return value passed to exit. */ int (*message_fatal_cleanup)(void) = NULL; /* If non-NULL, prepended (followed by ": ") to messages. */ const char *message_program_name = NULL; /* Whether or not we're currently outputting a particular type of trace. */ static bool tracing[TRACE_ALL] = { false /* false, ... */ }; /* ** Set the handlers for a particular message function. Takes a pointer to ** the handler list, the count of handlers, and the argument list. */ static void message_handlers(message_handler_func **list, int count, va_list args) { int i; if (*list != stdout_handlers && *list != stderr_handlers) free(*list); *list = xmalloc(sizeof(message_handler_func) * (count + 1)); for (i = 0; i < count; i++) (*list)[i] = (message_handler_func) va_arg(args, message_handler_func); (*list)[count] = NULL; } /* ** There's no good way of writing these handlers without a bunch of code ** duplication since we can't assume variadic macros, but I can at least make ** it easier to write and keep them consistent. */ #define HANDLER_FUNCTION(type) \ void \ message_handlers_ ## type(int count, ...) \ { \ va_list args; \ \ va_start(args, count); \ message_handlers(& type ## _handlers, count, args); \ va_end(args); \ } HANDLER_FUNCTION(debug) HANDLER_FUNCTION(trace) HANDLER_FUNCTION(notice) HANDLER_FUNCTION(warn) HANDLER_FUNCTION(die) /* ** Print a message to stdout, supporting message_program_name. */ void message_log_stdout(int len UNUSED, const char *fmt, va_list args, int err) { if (message_program_name != NULL) fprintf(stdout, "%s: ", message_program_name); vfprintf(stdout, fmt, args); if (err) fprintf(stdout, ": %s", strerror(err)); fprintf(stdout, "\n"); } /* ** Print a message to stderr, supporting message_program_name. Also flush ** stdout so that errors and regular output occur in the right order. */ void message_log_stderr(int len UNUSED, const char *fmt, va_list args, int err) { fflush(stdout); if (message_program_name != NULL) fprintf(stderr, "%s: ", message_program_name); vfprintf(stderr, fmt, args); if (err) fprintf(stderr, ": %s", strerror(err)); fprintf(stderr, "\n"); } /* ** Log a message to syslog. This is a helper function used to implement all ** of the syslog message log handlers. It takes the same arguments as a ** regular message handler function but with an additional priority ** argument. */ static void message_log_syslog(int pri, int len, const char *fmt, va_list args, int err) { char *buffer; buffer = malloc(len + 1); if (buffer == NULL) { fprintf(stderr, "failed to malloc %u bytes at %s line %d: %s", len + 1, __FILE__, __LINE__, strerror(errno)); exit(message_fatal_cleanup ? (*message_fatal_cleanup)() : 1); } vsnprintf(buffer, len + 1, fmt, args); syslog(pri, err ? "%s: %m" : "%s", buffer); free(buffer); } /* ** Do the same sort of wrapper to generate all of the separate syslog logging ** functions. */ #define SYSLOG_FUNCTION(name, type) \ void \ message_log_syslog_ ## name(int l, const char *f, va_list a, int e) \ { \ message_log_syslog(LOG_ ## type, l, f, a, e); \ } SYSLOG_FUNCTION(debug, DEBUG) SYSLOG_FUNCTION(info, INFO) SYSLOG_FUNCTION(notice, NOTICE) SYSLOG_FUNCTION(warning, WARNING) SYSLOG_FUNCTION(err, ERR) SYSLOG_FUNCTION(crit, CRIT) /* ** Enable or disable tracing for particular classes of messages. */ void message_trace_enable(enum message_trace type, bool enable) { if (type > TRACE_ALL) return; if (type == TRACE_ALL) { int i; for (i = 0; i < TRACE_ALL; i++) tracing[i] = enable; } else { tracing[type] = enable; } } /* ** All of the message handlers. There's a lot of code duplication here too, ** but each one is still *slightly* different and va_start has to be called ** multiple times, so it's hard to get rid of the duplication. */ #ifdef DEBUG void debug(const char *format, ...) { va_list args; message_handler_func *log; int length; if (debug_handlers == NULL) return; va_start(args, format); length = vsnprintf(NULL, 0, format, args); va_end(args); if (length < 0) return; for (log = debug_handlers; *log != NULL; log++) { va_start(args, format); (**log)(length, format, args, 0); va_end(args); } } #elif !INN_HAVE_C99_VAMACROS && !INN_HAVE_GNU_VAMACROS void debug(const char *format UNUSED, ...) { } #endif void trace(enum message_trace type, const char *format, ...) { va_list args; message_handler_func *log; int length; if (trace_handlers == NULL || !tracing[type]) return; va_start(args, format); length = vsnprintf(NULL, 0, format, args); va_end(args); if (length < 0) return; for (log = trace_handlers; *log != NULL; log++) { va_start(args, format); (**log)(length, format, args, 0); va_end(args); } } void notice(const char *format, ...) { va_list args; message_handler_func *log; int length; va_start(args, format); length = vsnprintf(NULL, 0, format, args); va_end(args); if (length < 0) return; for (log = notice_handlers; *log != NULL; log++) { va_start(args, format); (**log)(length, format, args, 0); va_end(args); } } void sysnotice(const char *format, ...) { va_list args; message_handler_func *log; int length; int error = errno; va_start(args, format); length = vsnprintf(NULL, 0, format, args); va_end(args); if (length < 0) return; for (log = notice_handlers; *log != NULL; log++) { va_start(args, format); (**log)(length, format, args, error); va_end(args); } } void warn(const char *format, ...) { va_list args; message_handler_func *log; int length; va_start(args, format); length = vsnprintf(NULL, 0, format, args); va_end(args); if (length < 0) return; for (log = warn_handlers; *log != NULL; log++) { va_start(args, format); (**log)(length, format, args, 0); va_end(args); } } void syswarn(const char *format, ...) { va_list args; message_handler_func *log; int length; int error = errno; va_start(args, format); length = vsnprintf(NULL, 0, format, args); va_end(args); if (length < 0) return; for (log = warn_handlers; *log != NULL; log++) { va_start(args, format); (**log)(length, format, args, error); va_end(args); } } void die(const char *format, ...) { va_list args; message_handler_func *log; int length; va_start(args, format); length = vsnprintf(NULL, 0, format, args); va_end(args); if (length >= 0) for (log = die_handlers; *log != NULL; log++) { va_start(args, format); (**log)(length, format, args, 0); va_end(args); } exit(message_fatal_cleanup ? (*message_fatal_cleanup)() : 1); } void sysdie(const char *format, ...) { va_list args; message_handler_func *log; int length; int error = errno; va_start(args, format); length = vsnprintf(NULL, 0, format, args); va_end(args); if (length >= 0) for (log = die_handlers; *log != NULL; log++) { va_start(args, format); (**log)(length, format, args, error); va_end(args); } exit(message_fatal_cleanup ? (*message_fatal_cleanup)() : 1); }