diff options
Diffstat (limited to 'lib/libpjdlog/pjdlog.c')
-rw-r--r-- | lib/libpjdlog/pjdlog.c | 809 |
1 files changed, 809 insertions, 0 deletions
diff --git a/lib/libpjdlog/pjdlog.c b/lib/libpjdlog/pjdlog.c new file mode 100644 index 000000000000..84615faccd27 --- /dev/null +++ b/lib/libpjdlog/pjdlog.c @@ -0,0 +1,809 @@ +/*- + * Copyright (c) 2009-2010 The FreeBSD Foundation + * Copyright (c) 2011 Pawel Jakub Dawidek <pjd@FreeBSD.org> + * All rights reserved. + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <assert.h> +#include <errno.h> +#include <libutil.h> +#include <limits.h> +#include <printf.h> +#include <stdarg.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#ifdef notyet +#include <robustio.h> +#endif + +#include "pjdlog.h" + +#ifndef MAX +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +#define PJDLOG_MAX_MSGSIZE 4096 + +#define PJDLOG_PREFIX_STACK 4 +#define PJDLOG_PREFIX_MAXSIZE 128 + +#define PJDLOG_NEVER_INITIALIZED 0 +#define PJDLOG_NOT_INITIALIZED 1 +#define PJDLOG_INITIALIZED 2 + +static int pjdlog_initialized = PJDLOG_NEVER_INITIALIZED; +static int pjdlog_mode, pjdlog_debug_level, pjdlog_sock; +static int pjdlog_prefix_current; +static char pjdlog_prefix[PJDLOG_PREFIX_STACK][PJDLOG_PREFIX_MAXSIZE]; + +static int +pjdlog_printf_arginfo_humanized_number(const struct printf_info *pi __unused, + size_t n, int *argt) +{ + + assert(n >= 1); + argt[0] = PA_INT | PA_FLAG_INTMAX; + return (1); +} + +static int +pjdlog_printf_render_humanized_number(struct __printf_io *io, + const struct printf_info *pi, const void * const *arg) +{ + char buf[5]; + intmax_t num; + int ret; + + num = *(const intmax_t *)arg[0]; + humanize_number(buf, sizeof(buf), (int64_t)num, "", HN_AUTOSCALE, + HN_NOSPACE | HN_DECIMAL); + ret = __printf_out(io, pi, buf, strlen(buf)); + __printf_flush(io); + return (ret); +} + +static int +pjdlog_printf_arginfo_sockaddr(const struct printf_info *pi __unused, + size_t n, int *argt) +{ + + assert(n >= 1); + argt[0] = PA_POINTER; + return (1); +} + +static int +pjdlog_printf_render_sockaddr_ip(struct __printf_io *io, + const struct printf_info *pi, const void * const *arg) +{ + const struct sockaddr_storage *ss; + char addr[MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)]; + int ret; + + ss = *(const struct sockaddr_storage * const *)arg[0]; + switch (ss->ss_family) { + case AF_INET: + { + const struct sockaddr_in *sin; + + sin = (const struct sockaddr_in *)ss; + if (inet_ntop(ss->ss_family, &sin->sin_addr, addr, + sizeof(addr)) == NULL) { + PJDLOG_ABORT("inet_ntop(AF_INET) failed: %s.", + strerror(errno)); + } + break; + } + case AF_INET6: + { + const struct sockaddr_in6 *sin; + + sin = (const struct sockaddr_in6 *)ss; + if (inet_ntop(ss->ss_family, &sin->sin6_addr, addr, + sizeof(addr)) == NULL) { + PJDLOG_ABORT("inet_ntop(AF_INET6) failed: %s.", + strerror(errno)); + } + break; + } + default: + snprintf(addr, sizeof(addr), "[unsupported family %hhu]", + ss->ss_family); + break; + } + ret = __printf_out(io, pi, addr, strlen(addr)); + __printf_flush(io); + return (ret); +} + +static int +pjdlog_printf_render_sockaddr(struct __printf_io *io, + const struct printf_info *pi, const void * const *arg) +{ + const struct sockaddr_storage *ss; + char buf[PATH_MAX]; + int ret; + + ss = *(const struct sockaddr_storage * const *)arg[0]; + switch (ss->ss_family) { + case AF_UNIX: + { + const struct sockaddr_un *sun; + + sun = (const struct sockaddr_un *)ss; + if (sun->sun_path[0] == '\0') + snprintf(buf, sizeof(buf), "N/A"); + else + snprintf(buf, sizeof(buf), "%s", sun->sun_path); + break; + } + case AF_INET: + { + char addr[INET_ADDRSTRLEN]; + const struct sockaddr_in *sin; + unsigned int port; + + sin = (const struct sockaddr_in *)ss; + port = ntohs(sin->sin_port); + if (inet_ntop(ss->ss_family, &sin->sin_addr, addr, + sizeof(addr)) == NULL) { + PJDLOG_ABORT("inet_ntop(AF_INET) failed: %s.", + strerror(errno)); + } + snprintf(buf, sizeof(buf), "%s:%u", addr, port); + break; + } + case AF_INET6: + { + char addr[INET6_ADDRSTRLEN]; + const struct sockaddr_in6 *sin; + unsigned int port; + + sin = (const struct sockaddr_in6 *)ss; + port = ntohs(sin->sin6_port); + if (inet_ntop(ss->ss_family, &sin->sin6_addr, addr, + sizeof(addr)) == NULL) { + PJDLOG_ABORT("inet_ntop(AF_INET6) failed: %s.", + strerror(errno)); + } + snprintf(buf, sizeof(buf), "[%s]:%u", addr, port); + break; + } + default: + snprintf(buf, sizeof(buf), "[unsupported family %hhu]", + ss->ss_family); + break; + } + ret = __printf_out(io, pi, buf, strlen(buf)); + __printf_flush(io); + return (ret); +} + +void +pjdlog_init(int mode) +{ + int saved_errno; + + assert(pjdlog_initialized == PJDLOG_NEVER_INITIALIZED || + pjdlog_initialized == PJDLOG_NOT_INITIALIZED); +#ifdef notyet + assert(mode == PJDLOG_MODE_STD || mode == PJDLOG_MODE_SYSLOG || + mode == PJDLOG_MODE_SOCK); +#else + assert(mode == PJDLOG_MODE_STD || mode == PJDLOG_MODE_SYSLOG); +#endif + + saved_errno = errno; + + if (pjdlog_initialized == PJDLOG_NEVER_INITIALIZED) { + __use_xprintf = 1; + register_printf_render_std("T"); + register_printf_render('N', + pjdlog_printf_render_humanized_number, + pjdlog_printf_arginfo_humanized_number); + register_printf_render('I', + pjdlog_printf_render_sockaddr_ip, + pjdlog_printf_arginfo_sockaddr); + register_printf_render('S', + pjdlog_printf_render_sockaddr, + pjdlog_printf_arginfo_sockaddr); + } + + if (mode == PJDLOG_MODE_SYSLOG) + openlog(NULL, LOG_PID | LOG_NDELAY, LOG_LOCAL0); + pjdlog_mode = mode; + pjdlog_debug_level = 0; + pjdlog_prefix_current = 0; + pjdlog_prefix[0][0] = '\0'; + + pjdlog_initialized = PJDLOG_INITIALIZED; + pjdlog_sock = -1; + + errno = saved_errno; +} + +void +pjdlog_fini(void) +{ + int saved_errno; + + assert(pjdlog_initialized == PJDLOG_INITIALIZED); + + saved_errno = errno; + + if (pjdlog_mode == PJDLOG_MODE_SYSLOG) + closelog(); + + pjdlog_initialized = PJDLOG_NOT_INITIALIZED; + pjdlog_sock = -1; + + errno = saved_errno; +} + +/* + * Configure where the logs should go. + * By default they are send to stdout/stderr, but after going into background + * (eg. by calling daemon(3)) application is responsible for changing mode to + * PJDLOG_MODE_SYSLOG, so logs will be send to syslog. + */ +void +pjdlog_mode_set(int mode) +{ + int saved_errno; + + assert(pjdlog_initialized == PJDLOG_INITIALIZED); +#ifdef notyet + assert(mode == PJDLOG_MODE_STD || mode == PJDLOG_MODE_SYSLOG || + mode == PJDLOG_MODE_SOCK); +#else + assert(mode == PJDLOG_MODE_STD || mode == PJDLOG_MODE_SYSLOG); +#endif + + if (pjdlog_mode == mode) + return; + + saved_errno = errno; + + if (mode == PJDLOG_MODE_SYSLOG) + openlog(NULL, LOG_PID | LOG_NDELAY, LOG_DAEMON); + else if (mode == PJDLOG_MODE_STD) + closelog(); + + if (mode != PJDLOG_MODE_SOCK) + pjdlog_sock = -1; + + pjdlog_mode = mode; + + errno = saved_errno; +} + + +/* + * Return current mode. + */ +int +pjdlog_mode_get(void) +{ + + assert(pjdlog_initialized == PJDLOG_INITIALIZED); + + return (pjdlog_mode); +} + +#ifdef notyet +/* + * Sets socket number to use for PJDLOG_MODE_SOCK mode. + */ +void +pjdlog_sock_set(int sock) +{ + + assert(pjdlog_initialized == PJDLOG_INITIALIZED); + assert(pjdlog_mode == PJDLOG_MODE_SOCK); + assert(sock >= 0); + + pjdlog_sock = sock; +} +#endif + +#ifdef notyet +/* + * Returns socket number used for PJDLOG_MODE_SOCK mode. + */ +int +pjdlog_sock_get(void) +{ + + assert(pjdlog_initialized == PJDLOG_INITIALIZED); + assert(pjdlog_mode == PJDLOG_MODE_SOCK); + assert(pjdlog_sock >= 0); + + return (pjdlog_sock); +} +#endif + +/* + * Set debug level. All the logs above the level specified here will be + * ignored. + */ +void +pjdlog_debug_set(int level) +{ + + assert(pjdlog_initialized == PJDLOG_INITIALIZED); + assert(level >= 0); + assert(level <= 127); + + pjdlog_debug_level = level; +} + +/* + * Return current debug level. + */ +int +pjdlog_debug_get(void) +{ + + assert(pjdlog_initialized == PJDLOG_INITIALIZED); + + return (pjdlog_debug_level); +} + +/* + * Set prefix that will be used before each log. + */ +void +pjdlog_prefix_set(const char *fmt, ...) +{ + va_list ap; + + assert(pjdlog_initialized == PJDLOG_INITIALIZED); + + va_start(ap, fmt); + pjdlogv_prefix_set(fmt, ap); + va_end(ap); +} + +/* + * Set prefix that will be used before each log. + */ +void +pjdlogv_prefix_set(const char *fmt, va_list ap) +{ + int saved_errno; + + assert(pjdlog_initialized == PJDLOG_INITIALIZED); + assert(fmt != NULL); + + saved_errno = errno; + + vsnprintf(pjdlog_prefix[pjdlog_prefix_current], + sizeof(pjdlog_prefix[pjdlog_prefix_current]), fmt, ap); + + errno = saved_errno; +} + +/* + * Get current prefix. + */ +const char * +pjdlog_prefix_get(void) +{ + + assert(pjdlog_initialized == PJDLOG_INITIALIZED); + + return (pjdlog_prefix[pjdlog_prefix_current]); +} + +/* + * Set new prefix and put the current one on the stack. + */ +void +pjdlog_prefix_push(const char *fmt, ...) +{ + va_list ap; + + assert(pjdlog_initialized == PJDLOG_INITIALIZED); + + va_start(ap, fmt); + pjdlogv_prefix_push(fmt, ap); + va_end(ap); +} + +/* + * Set new prefix and put the current one on the stack. + */ +void +pjdlogv_prefix_push(const char *fmt, va_list ap) +{ + + assert(pjdlog_initialized == PJDLOG_INITIALIZED); + assert(pjdlog_prefix_current < PJDLOG_PREFIX_STACK - 1); + + pjdlog_prefix_current++; + + pjdlogv_prefix_set(fmt, ap); +} + +/* + * Removes current prefix and recovers previous one from the stack. + */ +void +pjdlog_prefix_pop(void) +{ + + assert(pjdlog_initialized == PJDLOG_INITIALIZED); + assert(pjdlog_prefix_current > 0); + + pjdlog_prefix_current--; +} + +/* + * Convert log level into string. + */ +static const char * +pjdlog_level_to_string(int loglevel) +{ + + switch (loglevel) { + case LOG_EMERG: + return ("EMERG"); + case LOG_ALERT: + return ("ALERT"); + case LOG_CRIT: + return ("CRIT"); + case LOG_ERR: + return ("ERROR"); + case LOG_WARNING: + return ("WARNING"); + case LOG_NOTICE: + return ("NOTICE"); + case LOG_INFO: + return ("INFO"); + case LOG_DEBUG: + return ("DEBUG"); + } + assert(!"Invalid log level."); + abort(); /* XXX: gcc */ +} + +static int +vsnprlcat(char *str, size_t size, const char *fmt, va_list ap) +{ + size_t len; + + len = strlen(str); + assert(len < size); + return (vsnprintf(str + len, size - len, fmt, ap)); +} + +static int +snprlcat(char *str, size_t size, const char *fmt, ...) +{ + va_list ap; + int result; + + va_start(ap, fmt); + result = vsnprlcat(str, size, fmt, ap); + va_end(ap); + return (result); +} + +static void +pjdlogv_common_single_line(const char *func, const char *file, int line, + int loglevel, int debuglevel, int error, const char *msg) +{ + static char log[2 * PJDLOG_MAX_MSGSIZE]; + char *logp; + size_t logs; + + assert(pjdlog_initialized == PJDLOG_INITIALIZED); +#ifdef notyet + assert(pjdlog_mode == PJDLOG_MODE_STD || + pjdlog_mode == PJDLOG_MODE_SYSLOG || + pjdlog_mode == PJDLOG_MODE_SOCK); +#else + assert(pjdlog_mode == PJDLOG_MODE_STD || + pjdlog_mode == PJDLOG_MODE_SYSLOG); +#endif + assert(pjdlog_mode != PJDLOG_MODE_SOCK || pjdlog_sock >= 0); + assert(loglevel == LOG_EMERG || loglevel == LOG_ALERT || + loglevel == LOG_CRIT || loglevel == LOG_ERR || + loglevel == LOG_WARNING || loglevel == LOG_NOTICE || + loglevel == LOG_INFO || loglevel == LOG_DEBUG); + assert(loglevel != LOG_DEBUG || debuglevel > 0); + assert(loglevel != LOG_DEBUG || debuglevel <= pjdlog_debug_level); + assert(debuglevel <= 127); + assert(error >= -1); + assert((file != NULL && line > 0) || + (func == NULL && file == NULL && line == 0)); + + switch (pjdlog_mode) { + case PJDLOG_MODE_STD: + case PJDLOG_MODE_SYSLOG: + logp = log; + logs = sizeof(log); + break; + case PJDLOG_MODE_SOCK: + logp = log + 4; + logs = sizeof(log) - 4; + break; + default: + assert(!"Invalid mode."); + } + + *logp = '\0'; + + if (pjdlog_mode != PJDLOG_MODE_SOCK) { + if (loglevel == LOG_DEBUG) { + /* Attach debuglevel if this is debug log. */ + snprlcat(logp, logs, "[%s%d] ", + pjdlog_level_to_string(loglevel), debuglevel); + } else { + snprlcat(logp, logs, "[%s] ", + pjdlog_level_to_string(loglevel)); + } + if (pjdlog_mode != PJDLOG_MODE_SYSLOG && + pjdlog_debug_level >= 1) { + snprlcat(logp, logs, "(pid=%d) ", getpid()); + } + } + /* Attach file, func, line if debuglevel is 2 or more. */ + if (pjdlog_debug_level >= 2 && file != NULL) { + if (func == NULL) + snprlcat(logp, logs, "(%s:%d) ", file, line); + else + snprlcat(logp, logs, "(%s:%d:%s) ", file, line, func); + } + + if (pjdlog_mode != PJDLOG_MODE_SOCK) { + snprlcat(logp, logs, "%s", + pjdlog_prefix[pjdlog_prefix_current]); + } + + strlcat(logp, msg, logs); + + /* Attach error description. */ + if (error != -1) + snprlcat(logp, logs, ": %s.", strerror(error)); + + switch (pjdlog_mode) { + case PJDLOG_MODE_STD: + fprintf(stderr, "%s\n", logp); + fflush(stderr); + break; + case PJDLOG_MODE_SYSLOG: + syslog(loglevel, "%s", logp); + break; +#ifdef notyet + case PJDLOG_MODE_SOCK: + { + char ack[2]; + uint16_t dlen; + + log[2] = loglevel; + log[3] = debuglevel; + dlen = strlen(logp) + 3; /* +3 = loglevel, debuglevel and terminating \0 */ + bcopy(&dlen, log, sizeof(dlen)); + if (robust_send(pjdlog_sock, log, (size_t)dlen + 2) == -1) /* +2 for size */ + assert(!"Unable to send log."); + if (robust_recv(pjdlog_sock, ack, sizeof(ack)) == -1) + assert(!"Unable to send log."); + break; + } +#endif + default: + assert(!"Invalid mode."); + } +} + +/* + * Common log routine, which can handle regular log level as well as debug + * level. We decide here where to send the logs (stdout/stderr or syslog). + */ +void +_pjdlogv_common(const char *func, const char *file, int line, int loglevel, + int debuglevel, int error, const char *fmt, va_list ap) +{ + char log[PJDLOG_MAX_MSGSIZE]; + char *logp, *curline; + const char *prvline; + int saved_errno; + + assert(pjdlog_initialized == PJDLOG_INITIALIZED); + assert(pjdlog_mode == PJDLOG_MODE_STD || + pjdlog_mode == PJDLOG_MODE_SYSLOG || + pjdlog_mode == PJDLOG_MODE_SOCK); + assert(pjdlog_mode != PJDLOG_MODE_SOCK || pjdlog_sock >= 0); + assert(loglevel == LOG_EMERG || loglevel == LOG_ALERT || + loglevel == LOG_CRIT || loglevel == LOG_ERR || + loglevel == LOG_WARNING || loglevel == LOG_NOTICE || + loglevel == LOG_INFO || loglevel == LOG_DEBUG); + assert(loglevel != LOG_DEBUG || debuglevel > 0); + assert(debuglevel <= 127); + assert(error >= -1); + + /* Ignore debug above configured level. */ + if (loglevel == LOG_DEBUG && debuglevel > pjdlog_debug_level) + return; + + saved_errno = errno; + + vsnprintf(log, sizeof(log), fmt, ap); + logp = log; + prvline = NULL; + + while ((curline = strsep(&logp, "\n")) != NULL) { + if (*curline == '\0') + continue; + if (prvline != NULL) { + pjdlogv_common_single_line(func, file, line, loglevel, + debuglevel, -1, prvline); + } + prvline = curline; + } + if (prvline == NULL) + prvline = ""; + pjdlogv_common_single_line(func, file, line, loglevel, debuglevel, + error, prvline); + + errno = saved_errno; +} + +/* + * Common log routine. + */ +void +_pjdlog_common(const char *func, const char *file, int line, int loglevel, + int debuglevel, int error, const char *fmt, ...) +{ + va_list ap; + + assert(pjdlog_initialized == PJDLOG_INITIALIZED); + + va_start(ap, fmt); + _pjdlogv_common(func, file, line, loglevel, debuglevel, error, fmt, ap); + va_end(ap); +} + +/* + * Log error, errno and exit. + */ +void +_pjdlogv_exit(const char *func, const char *file, int line, int exitcode, + int error, const char *fmt, va_list ap) +{ + + assert(pjdlog_initialized == PJDLOG_INITIALIZED); + + _pjdlogv_common(func, file, line, exitcode == 0 ? LOG_INFO : LOG_ERR, 0, + error, fmt, ap); + exit(exitcode); + /* NOTREACHED */ +} + +/* + * Log error, errno and exit. + */ +void +_pjdlog_exit(const char *func, const char *file, int line, int exitcode, + int error, const char *fmt, ...) +{ + va_list ap; + + assert(pjdlog_initialized == PJDLOG_INITIALIZED); + + va_start(ap, fmt); + _pjdlogv_exit(func, file, line, exitcode, error, fmt, ap); + /* NOTREACHED */ + va_end(ap); +} + +/* + * Log failure message and exit. + */ +void +_pjdlog_abort(const char *func, const char *file, int line, + int error, const char *failedexpr, const char *fmt, ...) +{ + va_list ap; + + assert(pjdlog_initialized == PJDLOG_INITIALIZED); + + /* + * Set pjdlog_debug_level to 2, so that file, line and func are + * included in log. This is fine as we will exit anyway. + */ + if (pjdlog_debug_level < 2) + pjdlog_debug_level = 2; + + /* + * When there is no message we pass __func__ as 'fmt'. + * It would be cleaner to pass NULL or "", but gcc generates a warning + * for both of those. + */ + if (fmt != func) { + va_start(ap, fmt); + _pjdlogv_common(func, file, line, LOG_CRIT, 0, -1, fmt, ap); + va_end(ap); + } + if (failedexpr == NULL) { + _pjdlog_common(func, file, line, LOG_CRIT, 0, -1, "Aborted."); + } else { + _pjdlog_common(func, file, line, LOG_CRIT, 0, -1, + "Assertion failed: (%s).", failedexpr); + } + if (error != -1) + _pjdlog_common(func, file, line, LOG_CRIT, 0, error, "Errno"); + abort(); +} + +#ifdef notyet +/* + * Receive log from the given socket. + */ +int +pjdlog_receive(int sock) +{ + char log[PJDLOG_MAX_MSGSIZE]; + int loglevel, debuglevel; + uint16_t dlen; + + if (robust_recv(sock, &dlen, sizeof(dlen)) == -1) + return (-1); + + PJDLOG_ASSERT(dlen > 0); + PJDLOG_ASSERT(dlen <= PJDLOG_MAX_MSGSIZE - 3); + + if (robust_recv(sock, log, (size_t)dlen) == -1) + return (-1); + + log[dlen - 1] = '\0'; + loglevel = log[0]; + debuglevel = log[1]; + _pjdlog_common(NULL, NULL, 0, loglevel, debuglevel, -1, "%s", log + 2); + + if (robust_send(sock, "ok", 2) == -1) + return (-1); + + return (0); +} +#endif |