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/logmsg.h b/include/igloo/logmsg.h new file mode 100644 index 0000000..62ee116 --- /dev/null +++ b/include/igloo/logmsg.h @@ -0,0 +1,122 @@ +/* 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, ...); + + +int igloo_logmsg_get_context(igloo_logmsg_t *msg, const char **msgid, const char **cat, const char **func, const char **codefile, const ssize_t *codeline, struct timespec **ts); +int igloo_logmsg_get_message(igloo_logmsg_t *msg, igloo_loglevel_t *level, const char **string); +int igloo_logmsg_get_extra(igloo_logmsg_t *msg, igloo_logmsg_opt_t *options, igloo_list_t **list); + +igloo_objecthandler_t * igloo_logmsg_formarter(igloo_ro_t backend, const char *subformat, const char *name, igloo_ro_t associated); +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..40d6e4e --- /dev/null +++ b/src/logmsg.c @@ -0,0 +1,198 @@ +/* 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 "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; +} + + +int igloo_logmsg_get_context(igloo_logmsg_t *msg, const char **msgid, const char **cat, const char **func, const char **codefile, const ssize_t *codeline, struct timespec **ts); +int igloo_logmsg_get_message(igloo_logmsg_t *msg, igloo_loglevel_t *level, const char **string); +int igloo_logmsg_get_extra(igloo_logmsg_t *msg, igloo_logmsg_opt_t *options, igloo_list_t **list); + +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 "<<>>"; +} + +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); + const char *level = NULL; + time_t now; + char pre[256+LOG_MAXLINELEN]; + int datelen; + + if (!msg) + return igloo_FILTER_RESULT_DROP; + + + level = __level2str(msg->level); + + 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:%zi) %s\n", level, msg->cat, msg->func, msg->codefile, msg->codeline, msg->string); + igloo_io_write(igloo_RO_TO_TYPE(*backend_object, igloo_io_t), pre, strlen(pre)); + + return igloo_FILTER_RESULT_PASS; +} + +static const igloo_objecthandler_ifdesc_t igloo_logmsg_formarter_ifdesc = { + igloo_INTERFACE_DESCRIPTION_BASE(igloo_objecthandler_ifdesc_t), + .is_thread_safe = 1, + .handle = __handle +}; + +typedef enum { + igloo_FST_NORMAL +} igloo_logmsg_formarter_subtype_t; + +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 (!igloo_RO_IS_VALID(backend, igloo_io_t)) + return NULL; + + if (!subformat || strcmp(subformat, "default") == 0) + subformat = "normal"; + + if (strcmp(subformat, "normal") == 0) { + sf = malloc(sizeof(*sf)); + if (!sf) + return NULL; + + *sf = igloo_FST_NORMAL; + } else { + return NULL; + } + + objecthandler = igloo_objecthandler_new(&igloo_logmsg_formarter_ifdesc, backend, sf, name, associated); + if (!objecthandler) { + free(sf); + } + + return objecthandler; +} + +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); 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