diff --git a/Makefile.am b/Makefile.am index 70a50d0..fa82b24 100644 --- a/Makefile.am +++ b/Makefile.am @@ -25,11 +25,13 @@ pkginclude_HEADERS = \ include/igloo/stdio.h \ include/igloo/filter.h \ include/igloo/objecthandler.h \ + include/igloo/logmsg.h \ include/igloo/buffer.h \ include/igloo/list.h \ include/igloo/reportxml.h libigloo_la_SOURCES = \ + src/private.c \ src/libigloo.c \ src/interface.c \ src/ro.c \ @@ -37,6 +39,7 @@ libigloo_la_SOURCES = \ src/stdio.c \ src/filter.c \ src/objecthandler.c \ + src/logmsg.c \ src/buffer.c \ src/list.c \ src/reportxml.c diff --git a/include/igloo/io.h b/include/igloo/io.h index 9ad54d7..4f6de8c 100644 --- a/include/igloo/io.h +++ b/include/igloo/io.h @@ -82,7 +82,7 @@ typedef unsigned long int igloo_io_opflag_t; /* Operate on metadata. */ #define igloo_IO_OPFLAG_METADATA ((igloo_io_opflag_t)0x0002) /* Operate on data and metadata. */ -#define igloo_IO_OPFLAG_FULL (igloo_IO_OPFLAG_DATAONLY|igloo_IO_OPFLAG_METADATAONLY) +#define igloo_IO_OPFLAG_FULL (igloo_IO_OPFLAG_DATA|igloo_IO_OPFLAG_METADATA) /* Instructs the operation that it should ignore the output state of the object. * This may improve performance as buffer flushes may be skipped. * However with this set any external software interacting with this object diff --git a/include/igloo/logmsg.h b/include/igloo/logmsg.h new file mode 100644 index 0000000..e537b9e --- /dev/null +++ b/include/igloo/logmsg.h @@ -0,0 +1,194 @@ +/* Copyright (C) 2019 Philipp "ph3-der-loewe" Schafft + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _LIBIGLOO__LOGMSG_H_ +#define _LIBIGLOO__LOGMSG_H_ +/** + * @file + * Put a good description of this file here + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#ifdef IGLOO_CTC_HAVE_STDINT_H +#include +#endif +#include + +#include +#include "ro.h" +#include "list.h" + +/* About thread safety: + * This set of functions is thread safe. + */ + +igloo_RO_FORWARD_TYPE(igloo_logmsg_t); + +/* Log level for log messages */ +typedef enum { + /* Used to report errors with function calls. */ + igloo_LOGLEVEL__ERROR = -1, + /* Unset log level. */ + igloo_LOGLEVEL__NONE = 0, + /* Logmsg reports an error. */ + igloo_LOGLEVEL_ERROR, + /* Logmsg reports a warning. */ + igloo_LOGLEVEL_WARN, + /* Logmsg is of information level. */ + igloo_LOGLEVEL_INFO, + /* Logmsg is for debugging only. */ + igloo_LOGLEVEL_DEBUG +} igloo_loglevel_t; + +/* Type for logmsg options. + */ +#ifdef IGLOO_CTC_HAVE_STDINT_H +typedef uint_least32_t igloo_logmsg_opt_t; +#else +typedef unsigned long int igloo_logmsg_opt_t; +#endif + +/* No options set. */ +#define igloo_LOGMSG_OPT_NONE ((igloo_logmsg_opt_t)0x000) +/* Logmsg is only useful for developing the software itself. */ +#define igloo_LOGMSG_OPT_DEVEL ((igloo_logmsg_opt_t)0x001) +/* Logmsg should be acknowledged by the user. */ +#define igloo_LOGMSG_OPT_ASKACK ((igloo_logmsg_opt_t)0x002) + +/* This creates a new log message. + * Parameters: + * name, associated + * See refobject_new(). + * msgid + * Message ID used to correlate messages of the same type. + * Used e.g. with igloo_LOGMSG_OPT_ASKACK. + * Must be globally unique. Could be URI (e.g. UUID based URN) + * or message@domain. + * cat + * Message category/module. + * func + * Function generating the log message. + * codefile + * Code file generating the message. + * codeline + * Code line generating the message. + * ts + * Timestamp of the message. If NULL a timestamp is generated. + * level + * Loglevel of the message. + * options + * Message and delivery options. + * referenced + * List of objects relevant to the context generating the message. + * format + * Format string for the log message. + * ... + * Parameters according to the format string. + */ +igloo_logmsg_t * igloo_logmsg_new(const char *name, igloo_ro_t associated, const char *msgid, const char *cat, const char *func, const char *codefile, const ssize_t codeline, const struct timespec * ts, igloo_loglevel_t level, igloo_logmsg_opt_t options, igloo_list_t *referenced, const char *format, ...); + +/* Get the context from a log message object. + * + * Any parameter but the msg parameter can be NULL if the caller is not interested in the specific value. + * In that case the value is not returned. + * + * Note: Strings are returned as pointers to internal memory. Those pointers become invalide + * once the caller releases it's reference to the message. + * + * Parameters: + * msg + * The log message to operate on. + * msgid, cat, func, codefile, codeline, ts + * Pointers to where the context should be stored. + */ +int igloo_logmsg_get_context(igloo_logmsg_t *msg, const char **msgid, const char **cat, const char **func, const char **codefile, ssize_t *codeline, struct timespec *ts); + +/* Get the message from a log message object. + * + * Any parameter but the msg parameter can be NULL if the caller is not interested in the specific value. + * In that case the value is not returned. + * + * Note: Strings are returned as pointers to internal memory. Those pointers become invalide + * once the caller releases it's reference to the message. + * + * Parameters: + * msg + * The log message to operate on. + * level, string + * Pointers to where the message should be stored. + */ +int igloo_logmsg_get_message(igloo_logmsg_t *msg, igloo_loglevel_t *level, const char **string); + +/* Get extra information from a log message object. + * + * Any parameter but the msg parameter can be NULL if the caller is not interested in the specific value. + * In that case the value is not returned. + * + * Parameters: + * msg + * The log message to operate on. + * options + * Options set on the message. + * referenced + * A list of referenced objects. A new reference to that list is returned. + */ +int igloo_logmsg_get_extra(igloo_logmsg_t *msg, igloo_logmsg_opt_t *options, igloo_list_t **referenced); + +/* This creates a formater that allows writing of log messages to a logfile. + * Parameters: + * backend + * The backend to write data to. Must be a igloo_io_t handle. + * subformat + * Subformat to use. NULL for default. + * Must be NULL. + * name, associated + * See refobject_new(). + */ +igloo_objecthandler_t * igloo_logmsg_formarter(igloo_ro_t backend, const char *subformat, const char *name, igloo_ro_t associated); + +/* This creates a filter for log messages. + * Parameters: + * level_min + * Minimum log level. + * level_max + * Maximum log leve. + * options_required + * Options a message must have set. + * options_absent + * Options a message must not have set. + * ts_min + * Minimum timestamp or NULL. + * ts_max + * Maximum timestamp or NULL. + * cat + * Message category/module or NULL. + * name, associated + * See refobject_new(). + */ +igloo_filter_t * igloo_logmsg_filter(igloo_loglevel_t level_min, igloo_loglevel_t level_max, igloo_logmsg_opt_t options_required, igloo_logmsg_opt_t options_absent, const struct timespec * ts_min, const struct timespec * ts_max, const char *cat, const char *name, igloo_ro_t associated); + +#ifdef __cplusplus +} +#endif + +#endif /* ! _LIBIGLOO__LOGMSG_H_ */ diff --git a/include/igloo/types.h b/include/igloo/types.h index 9c35b33..a93138d 100644 --- a/include/igloo/types.h +++ b/include/igloo/types.h @@ -37,6 +37,7 @@ extern "C" { typedef struct igloo_io_tag igloo_io_t; typedef struct igloo_filter_tag igloo_filter_t; typedef struct igloo_objecthandler_tag igloo_objecthandler_t; +typedef struct igloo_logmsg_tag igloo_logmsg_t; typedef struct igloo_buffer_tag igloo_buffer_t; typedef struct igloo_list_tag igloo_list_t; @@ -58,6 +59,7 @@ typedef union __attribute__ ((__transparent_union__)) { igloo_RO_TYPE(igloo_io_t) igloo_RO_TYPE(igloo_filter_t) igloo_RO_TYPE(igloo_objecthandler_t) + igloo_RO_TYPE(igloo_logmsg_t) igloo_RO_TYPE(igloo_buffer_t) igloo_RO_TYPE(igloo_list_t) igloo_RO_TYPE(igloo_reportxml_t) diff --git a/log/log.c b/log/log.c index 1203fd6..3e22703 100644 --- a/log/log.c +++ b/log/log.c @@ -53,6 +53,8 @@ #include +#include "../src/private.h" + #define LOG_MAXLOGS 25 #define LOG_MAXLINELEN 1024 @@ -439,220 +441,6 @@ void igloo_log_contents (int log_id, char **_contents, unsigned int *_len) _unlock_logger (); } -static inline int __vsnprintf__is_print(int c, int allow_space) -{ - if ((c <= '"' || c == '`' || c == '\\') && !(allow_space && c == ' ')) { - return 0; - } else { - return 1; - } -} - -static inline size_t __vsnprintf__strlen(const char *str, int is_alt, int allow_space) -{ - size_t ret = 0; - - if (!str) { - if (is_alt) { - return strlen("-"); - } else { - return strlen("(null)"); - } - } - - for (; *str; str++) { - if (__vsnprintf__is_print(*str, allow_space)) { - ret += 1; - } else { - ret += 4; - } - } - - if (is_alt) { - ret += 2; - } - - return ret; -} - -static void __vsnprintf(char *str, size_t size, const char *format, va_list ap) { - static const char hextable[] = "0123456789abcdef"; - int in_block = 0; - int block_size = 0; - int block_len = 0; - int block_space = 0; - int block_alt = 0; - const char * arg; - char buf[80]; - - for (; *format && size; format++) - { - if ( !in_block ) - { - if ( *format == '%' ) { - in_block = 1; - block_size = 0; - block_len = 0; - block_space = 0; - block_alt = 0; - } - else - { - *(str++) = *format; - size--; - } - } - else - { - // TODO: %l*[sdupi] as well as %.4080s and "%.*s - arg = NULL; - switch (*format) - { - case 'l': - block_size++; - break; - case 'z': - block_size = 'z'; - break; - case '.': - // just ignore '.'. If somebody cares: fix it. - break; - case '*': - block_len = va_arg(ap, int); - break; - case ' ': - block_space = 1; - break; - case '#': - block_alt = 1; - break; - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - block_len = atoi(format); - for (; *format >= '0' && *format <= '9'; format++); - format--; - break; - case 'p': - snprintf(buf, sizeof(buf), "%p", (void*)va_arg(ap, void *)); - arg = buf; - case 'd': - case 'i': - case 'u': - if (!arg) - { - switch (block_size) - { - case 0: - if (*format == 'u') - snprintf(buf, sizeof(buf), "%u", (unsigned int)va_arg(ap, unsigned int)); - else - snprintf(buf, sizeof(buf), "%i", (int)va_arg(ap, int)); - break; - case 1: - if (*format == 'u') - snprintf(buf, sizeof(buf), "%lu", (unsigned long int)va_arg(ap, unsigned long int)); - else - snprintf(buf, sizeof(buf), "%li", (long int)va_arg(ap, long int)); - break; - case 2: - if (*format == 'u') - snprintf(buf, sizeof(buf), "%llu", (unsigned long long int)va_arg(ap, unsigned long long int)); - else - snprintf(buf, sizeof(buf), "%lli", (long long int)va_arg(ap, long long int)); - break; - case 'z': - /* We do not use 'z' type of snprintf() here as it is not safe to use on a few outdated platforms. */ - if (*format == 'u') - snprintf(buf, sizeof(buf), "%llu", (unsigned long long int)va_arg(ap, size_t)); - else - snprintf(buf, sizeof(buf), "%lli", (long long int)va_arg(ap, ssize_t)); - break; - default: - snprintf(buf, sizeof(buf), "<<>>"); - break; - } - arg = buf; - } - case 's': - case 'H': - // TODO. - if (!arg) - arg = va_arg(ap, const char *); - if (*format != 'H') { - block_alt = 0; - } - if (!arg && !block_alt) - arg = "(null)"; - if (!block_len) { - block_len = __vsnprintf__strlen(arg, block_alt, block_space); - } - - // the if() is the outer structure so the inner for() - // is branch optimized. - if (*format == 'H' && !arg) - { - if (size && block_len) { - *(str++) = '-'; - size--; - block_len--; - } - } - else if (*format == 'H') - { - if (block_alt && size && block_len) { - *(str++) = '"'; - size--; - block_len--; - } - for (; *arg && block_len && size; arg++, size--, block_len--) - { - if (!__vsnprintf__is_print(*arg, block_space)) { - if (size < 4 || block_len < 4) { - /* Use old system if we do not have space for new one */ - *(str++) = '.'; - } else { - *(str++) = '\\'; - *(str++) = 'x'; - *(str++) = hextable[(*arg >> 0) & 0x0F]; - *(str++) = hextable[(*arg >> 4) & 0x0F]; - /* Also count the additional chars for string size and block length */ - size -= 3; - block_len -= 3; - } - } else { - *(str++) = *arg; - } - } - if (block_alt && size && block_len) { - *(str++) = '"'; - size--; - block_len--; - } - } - else - { - for (; *arg && block_len && size; arg++, size--, block_len--) - *(str++) = *arg; - } - in_block = 0; - break; - } - } - } - - if ( !size ) - str--; - - *str = 0; -} - void igloo_log_write(int log_id, unsigned priority, const char *cat, const char *func, const char *fmt, ...) { @@ -669,7 +457,7 @@ void igloo_log_write(int log_id, unsigned priority, const char *cat, const char va_start(ap, fmt); - __vsnprintf(line, sizeof(line), fmt, ap); + igloo_private__vsnprintf(line, sizeof(line), fmt, ap); va_end(ap); now = time(NULL); @@ -696,7 +484,7 @@ void igloo_log_write_direct(int log_id, const char *fmt, ...) va_start(ap, fmt); _lock_logger(); - __vsnprintf(line, LOG_MAXLINELEN, fmt, ap); + igloo_private__vsnprintf(line, LOG_MAXLINELEN, fmt, ap); if (_log_open (log_id)) { int len = igloo_create_log_entry (log_id, "", line); diff --git a/src/logmsg.c b/src/logmsg.c new file mode 100644 index 0000000..6201031 --- /dev/null +++ b/src/logmsg.c @@ -0,0 +1,393 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2019, Philipp "ph3-der-loewe" Schafft , + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include "private.h" + +#define LOG_MAXLINELEN 1024 + +struct igloo_logmsg_tag { + igloo_ro_base_t __base; + char *msgid; + char *cat; + char *func; + char *codefile; + ssize_t codeline; + struct timespec ts; + igloo_loglevel_t level; + igloo_logmsg_opt_t options; + igloo_list_t *referenced; + char *string; +}; + +static void __free(igloo_ro_t self) +{ + igloo_logmsg_t *logmsg = igloo_RO_TO_TYPE(self, igloo_logmsg_t); + free(logmsg->msgid); + free(logmsg->cat); + free(logmsg->func); + free(logmsg->codefile); + free(logmsg->string); + igloo_ro_unref(logmsg->referenced); +} + +igloo_RO_PUBLIC_TYPE(igloo_logmsg_t, + igloo_RO_TYPEDECL_FREE(__free) + ); + + +igloo_logmsg_t * igloo_logmsg_new(const char *name, igloo_ro_t associated, + const char *msgid, + const char *cat, + const char *func, const char *codefile, const ssize_t codeline, + const struct timespec * ts, + igloo_loglevel_t level, igloo_logmsg_opt_t options, + igloo_list_t *referenced, + const char *format, ...) +{ + igloo_logmsg_t *logmsg = igloo_ro_new_raw(igloo_logmsg_t, name, associated); + va_list ap; + char string[LOG_MAXLINELEN]; + + if (!logmsg) + return NULL; + + logmsg->codeline = codeline; + logmsg->level = level; + logmsg->options = options; + + va_start(ap, format); + igloo_private__vsnprintf(string, sizeof(string), format, ap); + va_end(ap); + + do { +#define __set_str(x) \ + if (x) { \ + logmsg->x = strdup((x)); \ + if (!logmsg->x) \ + break; \ + } + + __set_str(msgid); + __set_str(cat); + __set_str(func); + __set_str(codefile); + + logmsg->string = strdup(string); + if (!logmsg->string) + break; + + if (ts) { + logmsg->ts = *ts; + } else { + if (clock_gettime(CLOCK_REALTIME, &(logmsg->ts)) != 0) + break; + } + + + if (referenced) { + if (igloo_ro_ref(referenced) != 0) + break; + + logmsg->referenced = referenced; + } + + return logmsg; + } while (0); + + igloo_ro_unref(logmsg); + return NULL; +} + + +#define __SETSTRING(x) \ + if ((x)) { \ + *(x) = msg->x; \ + } + +int igloo_logmsg_get_context(igloo_logmsg_t *msg, const char **msgid, const char **cat, const char **func, const char **codefile, ssize_t *codeline, struct timespec *ts) +{ + if (!msg) + return -1; + + __SETSTRING(msgid); + __SETSTRING(cat); + __SETSTRING(func); + __SETSTRING(codefile); + __SETSTRING(codeline); + __SETSTRING(ts); + + return 0; +} + +int igloo_logmsg_get_message(igloo_logmsg_t *msg, igloo_loglevel_t *level, const char **string) +{ + if (!msg) + return -1; + + if (level) + *level = msg->level; + + __SETSTRING(string); + + return 0; +} + +int igloo_logmsg_get_extra(igloo_logmsg_t *msg, igloo_logmsg_opt_t *options, igloo_list_t **referenced) +{ + if (!msg) + return -1; + + if (options) + *options = msg->options; + + if (referenced) { + if (msg->referenced) { + if (igloo_ro_ref(msg->referenced) != 0) + return -1; + + *referenced = msg->referenced; + } else { + *referenced = NULL; + } + } + + return 0; +} + +static const char * __level2str(igloo_loglevel_t level) +{ + switch (level) { + case igloo_LOGLEVEL__ERROR: return "<<>>"; break; + case igloo_LOGLEVEL__NONE: return "NONE"; break; + case igloo_LOGLEVEL_ERROR: return "EROR"; break; + case igloo_LOGLEVEL_WARN: return "WARN"; break; + case igloo_LOGLEVEL_INFO: return "INFO"; break; + case igloo_LOGLEVEL_DEBUG: return "DBUG"; break; + } + + return "<<>>"; +} + +typedef enum { + igloo_FST_FULL, + igloo_FST_OLD, + igloo_FST_NORMAL = igloo_FST_OLD +} igloo_logmsg_formarter_subtype_t; + +static igloo_filter_result_t __handle(igloo_INTERFACE_BASIC_ARGS, igloo_ro_t object) +{ + igloo_logmsg_t *msg = igloo_RO_TO_TYPE(object, igloo_logmsg_t); + igloo_logmsg_formarter_subtype_t sf = *(igloo_logmsg_formarter_subtype_t*)*backend_userdata; + const char *level = NULL; + time_t now; + char pre[256+LOG_MAXLINELEN] = ""; + int datelen; + char flags[3] = " "; + + if (!msg) + return igloo_FILTER_RESULT_DROP; + + level = __level2str(msg->level); + + switch (sf) { + case igloo_FST_FULL: + if (msg->options & igloo_LOGMSG_OPT_DEVEL) + flags[0] = 'D'; + + if (msg->options & igloo_LOGMSG_OPT_ASKACK) + flags[1] = 'A'; + + now = msg->ts.tv_sec; + datelen = strftime(pre, sizeof(pre), "[%Y-%m-%d %H:%M:%S UTC]", gmtime(&now)); + snprintf(pre+datelen, sizeof(pre)-datelen, " (%s) %s [%s] %s/%s(%s:%zi) %s\n", (msg->msgid ? msg->msgid : ""), level, flags, msg->cat, msg->func, msg->codefile, msg->codeline, msg->string); + break; + case igloo_FST_OLD: + now = msg->ts.tv_sec; + datelen = strftime(pre, sizeof(pre), "[%Y-%m-%d %H:%M:%S]", localtime(&now)); + snprintf(pre+datelen, sizeof(pre)-datelen, " %s %s/%s %s\n", level, msg->cat, msg->func, msg->string); + break; + } + + igloo_io_write(igloo_RO_TO_TYPE(*backend_object, igloo_io_t), pre, strlen(pre)); + + return igloo_FILTER_RESULT_PASS; +} + +static int __flush(igloo_INTERFACE_BASIC_ARGS) +{ + igloo_io_t *io = igloo_RO_TO_TYPE(*backend_object, igloo_io_t); + + if (!io) + return 0; + + return igloo_io_flush(io, igloo_IO_OPFLAG_DEFAULTS|igloo_IO_OPFLAG_FULL); +} + +static int __set_backend(igloo_INTERFACE_BASIC_ARGS, igloo_ro_t backend) +{ + int ret; + + if (!igloo_RO_IS_NULL(backend)) { + if (!igloo_RO_IS_VALID(backend, igloo_io_t)) + return -1; + + ret = igloo_ro_ref(backend); + if (ret != 0) + return ret; + } + + igloo_ro_unref(*backend_object); + *backend_object = backend; + + return 0; +} + + +static const igloo_objecthandler_ifdesc_t igloo_logmsg_formarter_ifdesc = { + igloo_INTERFACE_DESCRIPTION_BASE(igloo_objecthandler_ifdesc_t), + .is_thread_safe = 1, + .handle = __handle, + .flush = __flush, + .set_backend = __set_backend +}; + +igloo_objecthandler_t * igloo_logmsg_formarter(igloo_ro_t backend, const char *subformat, const char *name, igloo_ro_t associated) +{ + igloo_logmsg_formarter_subtype_t *sf = NULL; + igloo_objecthandler_t *objecthandler; + + if (!subformat || strcmp(subformat, "default") == 0) + subformat = "normal"; + + sf = malloc(sizeof(*sf)); + if (!sf) + return NULL; + + if (strcmp(subformat, "normal") == 0) { + *sf = igloo_FST_NORMAL; + } else if (strcmp(subformat, "full") == 0) { + *sf = igloo_FST_FULL; + } else if (strcmp(subformat, "old") == 0) { + *sf = igloo_FST_OLD; + } else { + free(sf); + return NULL; + } + + objecthandler = igloo_objecthandler_new(&igloo_logmsg_formarter_ifdesc, NULL, sf, name, associated); + if (!objecthandler) { + free(sf); + } + + if (igloo_objecthandler_set_backend(objecthandler, backend) != 0) { + igloo_ro_unref(objecthandler); + return NULL; + } + + return objecthandler; +} + +typedef struct { + igloo_loglevel_t level_min; + igloo_loglevel_t level_max; + igloo_logmsg_opt_t options_required; + igloo_logmsg_opt_t options_absent; + struct timespec ts_min; + struct timespec ts_max; + char *cat; +} igloo_logmsg_filter_mask_t; + +static inline int __gt_timespec(struct timespec *a, struct timespec *b) +{ + return a->tv_sec > b->tv_sec || (a->tv_sec == b->tv_sec && a->tv_nsec > b->tv_nsec); +} + +static igloo_filter_result_t __test(igloo_INTERFACE_BASIC_ARGS, igloo_ro_t object) +{ + igloo_logmsg_t *msg = igloo_RO_TO_TYPE(object, igloo_logmsg_t); + igloo_logmsg_filter_mask_t *mask = *backend_userdata; + + if (!msg) + return igloo_FILTER_RESULT_DROP; + + if (msg->level < mask->level_min || msg->level > mask->level_max) + return igloo_FILTER_RESULT_DROP; + + if ((msg->options & mask->options_required) != mask->options_required || (msg->options & mask->options_absent)) + return igloo_FILTER_RESULT_DROP; + + if (__gt_timespec(&(mask->ts_min), &(msg->ts)) || __gt_timespec(&(msg->ts), &(mask->ts_max))) + return igloo_FILTER_RESULT_DROP; + + if (mask->cat) { + if (!msg->cat) + return igloo_FILTER_RESULT_DROP; + + if (strcmp(msg->cat, mask->cat) != 0) + return igloo_FILTER_RESULT_DROP; + } + + return igloo_FILTER_RESULT_PASS; +} + +static const igloo_filter_ifdesc_t igloo_logmsg_filter_ifdesc = { + igloo_INTERFACE_DESCRIPTION_BASE(igloo_filter_ifdesc_t), + .test = __test +}; + +igloo_filter_t * igloo_logmsg_filter(igloo_loglevel_t level_min, igloo_loglevel_t level_max, igloo_logmsg_opt_t options_required, igloo_logmsg_opt_t options_absent, const struct timespec * ts_min, const struct timespec * ts_max, const char *cat, const char *name, igloo_ro_t associated) +{ + igloo_filter_t *filter; + igloo_logmsg_filter_mask_t *mask = calloc(1, sizeof(*mask)); + + if (!mask) + return NULL; + + mask->level_min = level_min; + mask->level_max = level_max; + mask->options_required = options_required; + mask->options_absent = options_absent; + + /* No else needed, as {0, 0} is already in the past. */ + if (ts_min) + mask->ts_min = *ts_min; + + if (ts_max) { + mask->ts_max = *ts_max; + } else { + /* BEFORE YEAR 2038 IMPORTANT REWRITE: Update date. */ + mask->ts_max.tv_sec = 2145916800; /* this is 2038-01-01 */ + } + + if (cat) { + mask->cat = strdup(cat); + if (!mask->cat) { + free(mask); + return NULL; + } + } + + filter = igloo_filter_new(&igloo_logmsg_filter_ifdesc, igloo_RO_NULL, mask, name, associated); + if (!filter) { + free(mask); + } + return filter; +} diff --git a/src/objecthandler.c b/src/objecthandler.c index 515e9ac..bee50a2 100644 --- a/src/objecthandler.c +++ b/src/objecthandler.c @@ -31,6 +31,9 @@ static void __free(igloo_ro_t self) igloo_objecthandler_t *handler = igloo_RO_TO_TYPE(self, igloo_objecthandler_t); igloo_thread_rwlock_wlock(&(handler->rwlock)); + igloo_ro_unref(handler->filter_a); + igloo_ro_unref(handler->filter_b); + igloo_ro_unref(handler->filter_list); igloo_thread_rwlock_unlock(&(handler->rwlock)); igloo_thread_rwlock_destroy(&(handler->rwlock)); diff --git a/src/private.c b/src/private.c new file mode 100644 index 0000000..888a139 --- /dev/null +++ b/src/private.c @@ -0,0 +1,233 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2019, Philipp "ph3-der-loewe" Schafft , + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include "private.h" + +static inline int __vsnprintf__is_print(int c, int allow_space) +{ + if ((c <= '"' || c == '`' || c == '\\') && !(allow_space && c == ' ')) { + return 0; + } else { + return 1; + } +} + +static inline size_t __vsnprintf__strlen(const char *str, int is_alt, int allow_space) +{ + size_t ret = 0; + + if (!str) { + if (is_alt) { + return strlen("-"); + } else { + return strlen("(null)"); + } + } + + for (; *str; str++) { + if (__vsnprintf__is_print(*str, allow_space)) { + ret += 1; + } else { + ret += 4; + } + } + + if (is_alt) { + ret += 2; + } + + return ret; +} + +void igloo_private__vsnprintf(char *str, size_t size, const char *format, va_list ap) +{ + static const char hextable[] = "0123456789abcdef"; + int in_block = 0; + int block_size = 0; + int block_len = 0; + int block_space = 0; + int block_alt = 0; + const char * arg; + char buf[80]; + + for (; *format && size; format++) + { + if ( !in_block ) + { + if ( *format == '%' ) { + in_block = 1; + block_size = 0; + block_len = 0; + block_space = 0; + block_alt = 0; + } + else + { + *(str++) = *format; + size--; + } + } + else + { + // TODO: %l*[sdupi] as well as %.4080s and "%.*s + arg = NULL; + switch (*format) + { + case 'l': + block_size++; + break; + case 'z': + block_size = 'z'; + break; + case '.': + // just ignore '.'. If somebody cares: fix it. + break; + case '*': + block_len = va_arg(ap, int); + break; + case ' ': + block_space = 1; + break; + case '#': + block_alt = 1; + break; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + block_len = atoi(format); + for (; *format >= '0' && *format <= '9'; format++); + format--; + break; + case 'p': + snprintf(buf, sizeof(buf), "%p", (void*)va_arg(ap, void *)); + arg = buf; + case 'd': + case 'i': + case 'u': + if (!arg) + { + switch (block_size) + { + case 0: + if (*format == 'u') + snprintf(buf, sizeof(buf), "%u", (unsigned int)va_arg(ap, unsigned int)); + else + snprintf(buf, sizeof(buf), "%i", (int)va_arg(ap, int)); + break; + case 1: + if (*format == 'u') + snprintf(buf, sizeof(buf), "%lu", (unsigned long int)va_arg(ap, unsigned long int)); + else + snprintf(buf, sizeof(buf), "%li", (long int)va_arg(ap, long int)); + break; + case 2: + if (*format == 'u') + snprintf(buf, sizeof(buf), "%llu", (unsigned long long int)va_arg(ap, unsigned long long int)); + else + snprintf(buf, sizeof(buf), "%lli", (long long int)va_arg(ap, long long int)); + break; + case 'z': + /* We do not use 'z' type of snprintf() here as it is not safe to use on a few outdated platforms. */ + if (*format == 'u') + snprintf(buf, sizeof(buf), "%llu", (unsigned long long int)va_arg(ap, size_t)); + else + snprintf(buf, sizeof(buf), "%lli", (long long int)va_arg(ap, ssize_t)); + break; + default: + snprintf(buf, sizeof(buf), "<<>>"); + break; + } + arg = buf; + } + case 's': + case 'H': + // TODO. + if (!arg) + arg = va_arg(ap, const char *); + if (*format != 'H') { + block_alt = 0; + } + if (!arg && !block_alt) + arg = "(null)"; + if (!block_len) { + block_len = __vsnprintf__strlen(arg, block_alt, block_space); + } + + // the if() is the outer structure so the inner for() + // is branch optimized. + if (*format == 'H' && !arg) + { + if (size && block_len) { + *(str++) = '-'; + size--; + block_len--; + } + } + else if (*format == 'H') + { + if (block_alt && size && block_len) { + *(str++) = '"'; + size--; + block_len--; + } + for (; *arg && block_len && size; arg++, size--, block_len--) + { + if (!__vsnprintf__is_print(*arg, block_space)) { + if (size < 4 || block_len < 4) { + /* Use old system if we do not have space for new one */ + *(str++) = '.'; + } else { + *(str++) = '\\'; + *(str++) = 'x'; + *(str++) = hextable[(*arg >> 0) & 0x0F]; + *(str++) = hextable[(*arg >> 4) & 0x0F]; + /* Also count the additional chars for string size and block length */ + size -= 3; + block_len -= 3; + } + } else { + *(str++) = *arg; + } + } + if (block_alt && size && block_len) { + *(str++) = '"'; + size--; + block_len--; + } + } + else + { + for (; *arg && block_len && size; arg++, size--, block_len--) + *(str++) = *arg; + } + in_block = 0; + break; + } + } + } + + if ( !size ) + str--; + + *str = 0; +} + diff --git a/src/private.h b/src/private.h index 35d0791..2ba3ffc 100644 --- a/src/private.h +++ b/src/private.h @@ -19,6 +19,10 @@ #ifndef _LIBIGLOO__PRIVATE_H_ #define _LIBIGLOO__PRIVATE_H_ +#ifdef STDC_HEADERS +#include +#endif + #include /* init/shutdown of the library */ @@ -55,4 +59,6 @@ void igloo_interface_base_free(igloo_ro_t self); igloo_ro_t igloo_interface_base_new_real(const igloo_ro_type_t *type, size_t description_length, const igloo_interface_base_ifdesc_t *ifdesc, igloo_ro_t backend_object, void *backend_userdata, const char *name, igloo_ro_t associated); #define igloo_interface_base_new(type, ifdesc, backend_object, backend_userdata, name, associated) igloo_RO_TO_TYPE(igloo_interface_base_new_real(igloo_ro__type__ ## type, sizeof(*(ifdesc)), (const igloo_interface_base_ifdesc_t*)(ifdesc), (backend_object), (backend_userdata), (name), (associated)), type) +void igloo_private__vsnprintf(char *str, size_t size, const char *format, va_list ap); + #endif diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am index e9210ee..1fe43be 100644 --- a/src/tests/Makefile.am +++ b/src/tests/Makefile.am @@ -55,5 +55,19 @@ ctest_reportxml_test_LDADD = libice_ctest.la \ src/reportxml.o check_PROGRAMS += ctest_reportxml.test +ctest_logmsg_test_SOURCES = %reldir%/ctest_logmsg.c +ctest_logmsg_test_LDADD = libice_ctest.la \ + thread/libicethread.la \ + avl/libiceavl.la \ + src/private.o \ + src/ro.o \ + src/io.o \ + src/list.o \ + src/interface.o \ + src/objecthandler.o \ + src/filter.o \ + src/logmsg.o +check_PROGRAMS += ctest_logmsg.test + # Add all programs to TESTS TESTS = $(check_PROGRAMS) diff --git a/src/tests/ctest_logmsg.c b/src/tests/ctest_logmsg.c new file mode 100644 index 0000000..9aa8bb4 --- /dev/null +++ b/src/tests/ctest_logmsg.c @@ -0,0 +1,131 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2018, Philipp "ph3-der-loewe" Schafft , + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "ctest_lib.h" + +#include +#include + +static void test_create_unref(void) +{ + igloo_logmsg_t *msg; + igloo_objecthandler_t *formater; + igloo_filter_t *filter; + + msg = igloo_logmsg_new(NULL, igloo_RO_NULL, NULL, NULL, NULL, NULL, -1, NULL, igloo_LOGLEVEL__NONE, igloo_LOGMSG_OPT_NONE, NULL, "test"); + ctest_test("logmsg created", !igloo_RO_IS_NULL(msg)); + ctest_test("un-referenced", igloo_ro_unref(msg) == 0); + + formater = igloo_logmsg_formarter(igloo_RO_NULL, NULL, NULL, igloo_RO_NULL); + ctest_test("formater created", !igloo_RO_IS_NULL(formater)); + ctest_test("un-referenced", igloo_ro_unref(formater) == 0); + + filter = igloo_logmsg_filter(igloo_LOGLEVEL__NONE, igloo_LOGLEVEL__NONE, igloo_LOGMSG_OPT_NONE, igloo_LOGMSG_OPT_NONE, NULL, NULL, NULL, NULL, igloo_RO_NULL); + ctest_test("filter created", !igloo_RO_IS_NULL(filter)); + ctest_test("un-referenced", igloo_ro_unref(filter) == 0); +} + +static void test_logmsg(void) +{ + static const struct timespec tv_in = {.tv_sec = -4242134, .tv_nsec = 1234789}; + igloo_logmsg_t *msg; + const char *msgid_out, *cat_out, *func_out, *codefile_out, *string_out; + ssize_t codeline_out; + struct timespec tv_out; + igloo_loglevel_t level_out; + igloo_logmsg_opt_t options_out; + igloo_list_t *referenced_out; + int ret; + + msg = igloo_logmsg_new("name", igloo_RO_NULL, "msgid", "cat", "func", "codefile", 13374242, &tv_in, igloo_LOGLEVEL_INFO, igloo_LOGMSG_OPT_ASKACK, NULL, "test %i %s", 5, "msg"); + ctest_test("logmsg created", !igloo_RO_IS_NULL(msg)); + + ctest_test("got context", (ret = igloo_logmsg_get_context(msg, &msgid_out, &cat_out, &func_out, &codefile_out, &codeline_out, &tv_out)) == 0); + if (ret == 0) { + ctest_test("got msgid", msgid_out != NULL && strcmp(msgid_out, "msgid") == 0); + ctest_test("got cat", cat_out != NULL && strcmp(cat_out, "cat") == 0); + ctest_test("got func", func_out != NULL && strcmp(func_out, "func") == 0); + ctest_test("got codefile", codefile_out != NULL && strcmp(codefile_out, "codefile") == 0); + ctest_test("got codeline", codeline_out == 13374242); + ctest_test("got ts", tv_out.tv_sec == -4242134 && tv_out.tv_nsec == 1234789); + } else { + ctest_test("got msgid", 0); + ctest_test("got cat", 0); + ctest_test("got func", 0); + ctest_test("got codefile", 0); + ctest_test("got codeline", 0); + ctest_test("got ts", 0); + } + + ctest_test("got message", (ret = igloo_logmsg_get_message(msg, &level_out, &string_out)) == 0); + if (ret == 0) { + ctest_test("got level", level_out == igloo_LOGLEVEL_INFO); + ctest_test("got string", string_out != NULL && strcmp(string_out, "test 5 msg") == 0); + } else { + ctest_test("got level", 0); + ctest_test("got string", 0); + } + + ctest_test("got extra", (ret = igloo_logmsg_get_extra(msg, &options_out, &referenced_out)) == 0); + if (ret == 0) { + ctest_test("got options", options_out == igloo_LOGMSG_OPT_ASKACK); + ctest_test("got referenced", referenced_out == NULL); + } else { + ctest_test("got options", 0); + ctest_test("got referenced", 0); + } + + ctest_test("un-referenced", igloo_ro_unref(msg) == 0); +} + +static void test_filter(void) +{ + igloo_filter_t *filter; + igloo_logmsg_t *msg; + igloo_ro_base_t *base; + + filter = igloo_logmsg_filter(igloo_LOGLEVEL_ERROR, igloo_LOGLEVEL_WARN, igloo_LOGMSG_OPT_NONE, igloo_LOGMSG_OPT_NONE, NULL, NULL, NULL, NULL, igloo_RO_NULL); + ctest_test("filter created", !igloo_RO_IS_NULL(filter)); + + base = igloo_ro_new(igloo_ro_base_t); + ctest_test("base created", base != NULL); + ctest_test("droping base", igloo_filter_test(filter, base) == igloo_FILTER_RESULT_DROP); + ctest_test("base un-referenced", igloo_ro_unref(base) == 0); + + msg = igloo_logmsg_new(NULL, igloo_RO_NULL, NULL, NULL, NULL, NULL, -1, NULL, igloo_LOGLEVEL_INFO, igloo_LOGMSG_OPT_NONE, NULL, "test"); + ctest_test("logmsg created", !igloo_RO_IS_NULL(msg)); + ctest_test("droping logmsg", igloo_filter_test(filter, msg) == igloo_FILTER_RESULT_DROP); + ctest_test("un-referenced", igloo_ro_unref(msg) == 0); + + msg = igloo_logmsg_new(NULL, igloo_RO_NULL, NULL, NULL, NULL, NULL, -1, NULL, igloo_LOGLEVEL_WARN, igloo_LOGMSG_OPT_NONE, NULL, "test"); + ctest_test("logmsg created", !igloo_RO_IS_NULL(msg)); + ctest_test("passing logmsg", igloo_filter_test(filter, msg) == igloo_FILTER_RESULT_PASS); + ctest_test("un-referenced", igloo_ro_unref(msg) == 0); + + ctest_test("un-referenced", igloo_ro_unref(filter) == 0); +} + +int main (void) +{ + ctest_init(); + + test_create_unref(); + + test_logmsg(); + test_filter(); + + ctest_fin(); + + return 0; +}