/* * 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); * 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, 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). * * The canonical version of this file is maintained in the rra-c-util package, * which can be found at . * * Written by Russ Allbery * Copyright 2015-2016, 2020 Russ Allbery * Copyright 2008-2010, 2013-2014 * The Board of Trustees of the Leland Stanford Junior University * Copyright 2004-2006 Internet Systems Consortium, Inc. ("ISC") * Copyright 1991, 1994-2003 The Internet Software Consortium and Rich Salz * * This code is derived from software contributed to the Internet Software * Consortium by Rich Salz. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. * * SPDX-License-Identifier: ISC */ #include #include #include #ifdef HAVE_SYSLOG_H # include #endif #ifdef _WIN32 # include # define LOG_DEBUG EVENTLOG_SUCCESS # define LOG_INFO EVENTLOG_INFORMATION_TYPE # define LOG_NOTICE EVENTLOG_INFORMATION_TYPE # define LOG_WARNING EVENTLOG_WARNING_TYPE # define LOG_ERR EVENTLOG_ERROR_TYPE # define LOG_CRIT EVENTLOG_ERROR_TYPE #endif #include #include #include /* 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 *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; /* * 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, unsigned int count, va_list args) { unsigned int i; if (*list != stdout_handlers && *list != stderr_handlers) free(*list); *list = xcalloc(count + 1, sizeof(message_handler_func)); 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. */ /* clang-format off */ #define HANDLER_FUNCTION(type) \ void \ message_handlers_ ## type(unsigned int count, ...) \ { \ va_list args; \ \ va_start(args, count); \ message_handlers(& type ## _handlers, count, args); \ va_end(args); \ } /* clang-format on */ HANDLER_FUNCTION(debug) HANDLER_FUNCTION(notice) HANDLER_FUNCTION(warn) HANDLER_FUNCTION(die) /* * Reset all handlers back to the defaults and free all allocated memory. * This is primarily useful for programs that undergo comprehensive memory * allocation analysis. */ void message_handlers_reset(void) { free(debug_handlers); debug_handlers = NULL; if (notice_handlers != stdout_handlers) { free(notice_handlers); notice_handlers = stdout_handlers; } if (warn_handlers != stderr_handlers) { free(warn_handlers); warn_handlers = stderr_handlers; } if (die_handlers != stderr_handlers) { free(die_handlers); die_handlers = stderr_handlers; } } /* * Print a message to stdout, supporting message_program_name. */ void message_log_stdout(size_t 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"); fflush(stdout); } /* * 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(size_t 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. * * This needs further attention on Windows. For example, it currently doesn't * log the errno information. */ static void __attribute__((__format__(printf, 3, 0))) message_log_syslog(int pri, size_t len, const char *fmt, va_list args, int err) { char *buffer; int status; buffer = malloc(len + 1); if (buffer == NULL) { fprintf(stderr, "failed to malloc %lu bytes at %s line %d: %s", (unsigned long) len + 1, __FILE__, __LINE__, strerror(errno)); exit(message_fatal_cleanup ? (*message_fatal_cleanup)() : 1); } status = vsnprintf(buffer, len + 1, fmt, args); if (status < 0 || (size_t) status >= len + 1) { warn("failed to format output with vsnprintf in syslog handler"); free(buffer); return; } #ifdef _WIN32 { HANDLE eventlog; eventlog = RegisterEventSource(NULL, message_program_name); if (eventlog != NULL) { ReportEvent(eventlog, (WORD) pri, 0, 0, NULL, 1, 0, &buffer, NULL); CloseEventLog(eventlog); } } #else /* !_WIN32 */ if (err == 0) syslog(pri, "%s", buffer); else syslog(pri, "%s: %s", buffer, strerror(err)); #endif /* !_WIN32 */ free(buffer); } /* * Do the same sort of wrapper to generate all of the separate syslog logging * functions. */ /* clang-format off */ #define SYSLOG_FUNCTION(name, type) \ void \ message_log_syslog_ ## name(size_t 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) /* clang-format on */ /* * 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. */ void debug(const char *format, ...) { va_list args; message_handler_func *log; ssize_t 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)((size_t) length, format, args, 0); va_end(args); } } void notice(const char *format, ...) { va_list args; message_handler_func *log; ssize_t 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)((size_t) length, format, args, 0); va_end(args); } } void sysnotice(const char *format, ...) { va_list args; message_handler_func *log; ssize_t 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)((size_t) length, format, args, error); va_end(args); } } void warn(const char *format, ...) { va_list args; message_handler_func *log; ssize_t 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)((size_t) length, format, args, 0); va_end(args); } } void syswarn(const char *format, ...) { va_list args; message_handler_func *log; ssize_t 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)((size_t) length, format, args, error); va_end(args); } } void die(const char *format, ...) { va_list args; message_handler_func *log; ssize_t 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)((size_t) 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; ssize_t 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)((size_t) length, format, args, error); va_end(args); } exit(message_fatal_cleanup ? (*message_fatal_cleanup)() : 1); }