/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2009-2010 The FreeBSD Foundation * Copyright (c) 2011 Pawel Jakub Dawidek * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef notyet #include #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