/* * Copyright (C) 2009-2013 Tobias Brunner * Copyright (C) 2006-2008 Martin Willi * HSR Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program 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 General Public License * for more details. */ #include "printf_hook.h" #include #include #include #include #include #include #include typedef struct private_printf_hook_t private_printf_hook_t; typedef struct printf_hook_handler_t printf_hook_handler_t; #define PRINTF_BUF_LEN 8192 #define ARGS_MAX 3 /** * private data of printf_hook */ struct private_printf_hook_t { /** * public functions */ printf_hook_t public; }; /** * struct with information about a registered handler */ struct printf_hook_handler_t { /** * callback function */ printf_hook_function_t hook; /** * number of arguments */ int numargs; /** * types of the arguments, VSTR_TYPE_FMT_* */ int argtypes[ARGS_MAX]; /** * name required for Vstr */ char *name; }; /** * Data to pass to a printf hook. */ struct printf_hook_data_t { /** * Base to append printf to */ Vstr_base *base; /** * Position in base to write to */ size_t pos; }; /* A-Z | 6 other chars | a-z */ static printf_hook_handler_t *printf_hooks[58]; #define SPEC_TO_INDEX(spec) ((int)(spec) - (int)'A') /** * These are used below, whenever the public wrapper functions are called before * initialization or after destruction. */ #undef vprintf #undef vfprintf #undef vsnprintf /** * Vstr variant of print_in_hook() */ size_t print_in_hook(printf_hook_data_t *data, char *fmt, ...) { size_t written; va_list args; va_start(args, fmt); written = vstr_add_vfmt(data->base, data->pos, fmt, args); va_end(args); data->pos += written; return written; } /** * Vstr custom format specifier callback function. */ static int custom_fmt_cb(Vstr_base *base, size_t pos, Vstr_fmt_spec *fmt_spec) { int i; const void *args[ARGS_MAX]; printf_hook_spec_t spec; printf_hook_handler_t *handler; printf_hook_data_t data = { .base = base, .pos = pos, }; handler = printf_hooks[SPEC_TO_INDEX(fmt_spec->name[0])]; for (i = 0; i < handler->numargs; i++) { switch (handler->argtypes[i]) { case VSTR_TYPE_FMT_INT: args[i] = VSTR_FMT_CB_ARG_PTR(fmt_spec, i); break; case VSTR_TYPE_FMT_PTR_VOID: args[i] = &VSTR_FMT_CB_ARG_PTR(fmt_spec, i); break; } } spec.hash = fmt_spec->fmt_hash; spec.plus = fmt_spec->fmt_plus; spec.minus = fmt_spec->fmt_minus; spec.width = fmt_spec->obj_field_width; handler->hook(&data, &spec, args); return 1; } /** * Add a custom format handler to the given Vstr_conf object */ static void vstr_fmt_add_handler(Vstr_conf *conf, printf_hook_handler_t *handler) { int *at; at = handler->argtypes; switch (handler->numargs) { case 1: vstr_fmt_add(conf, handler->name, custom_fmt_cb, at[0], VSTR_TYPE_FMT_END); break; case 2: vstr_fmt_add(conf, handler->name, custom_fmt_cb, at[0], at[1], VSTR_TYPE_FMT_END); break; case 3: vstr_fmt_add(conf, handler->name, custom_fmt_cb, at[0], at[1], at[2], VSTR_TYPE_FMT_END); break; } } /** * Thread specific vstr config */ static thread_value_t *vstr_conf = NULL; /** * Create vstr config for current thread */ static Vstr_conf *create_vstr_conf() { Vstr_conf *conf; int i; conf = vstr_make_conf(); vstr_cntl_conf(conf, VSTR_CNTL_CONF_SET_FMT_CHAR_ESC, '%'); vstr_cntl_conf(conf, VSTR_CNTL_CONF_SET_TYPE_GRPALLOC_CACHE, VSTR_TYPE_CNTL_CONF_GRPALLOC_CSTR); vstr_cntl_conf(conf, VSTR_CNTL_CONF_SET_NUM_BUF_SZ, PRINTF_BUF_LEN); for (i = 0; i < countof(printf_hooks); i++) { if (printf_hooks[i]) { vstr_fmt_add_handler(conf, printf_hooks[i]); } } return conf; } /** * Get vstr config of current thread */ static inline Vstr_conf *get_vstr_conf() { Vstr_conf *conf = NULL; if (vstr_conf) { conf = (Vstr_conf*)vstr_conf->get(vstr_conf); if (!conf) { conf = create_vstr_conf(); vstr_conf->set(vstr_conf, conf); } } return conf; } /** * Wrapper functions for printf and alike */ int vstr_wrapper_printf(const char *format, ...) { int written; va_list args; va_start(args, format); written = vstr_wrapper_vprintf(format, args); va_end(args); return written; } int vstr_wrapper_fprintf(FILE *stream, const char *format, ...) { int written; va_list args; va_start(args, format); written = vstr_wrapper_vfprintf(stream, format, args); va_end(args); return written; } int vstr_wrapper_sprintf(char *str, const char *format, ...) { int written; va_list args; va_start(args, format); written = vstr_wrapper_vsprintf(str, format, args); va_end(args); return written; } int vstr_wrapper_snprintf(char *str, size_t size, const char *format, ...) { int written; va_list args; va_start(args, format); written = vstr_wrapper_vsnprintf(str, size, format, args); va_end(args); return written; } int vstr_wrapper_asprintf(char **str, const char *format, ...) { int written; va_list args; va_start(args, format); written = vstr_wrapper_vasprintf(str, format, args); va_end(args); return written; } static inline int vstr_wrapper_vprintf_internal(Vstr_conf *conf, FILE *stream, const char *format, va_list args) { struct iovec *iov; int iovcnt, written = 0; Vstr_base *s; s = vstr_make_base(conf); vstr_add_vfmt(s, 0, format, args); if (vstr_export_iovec_ptr_all(s, &iov, &iovcnt)) { while (iovcnt--) { if (iov->iov_base) { written += fwrite(iov->iov_base, 1, iov->iov_len, stream); } iov++; } } vstr_free_base(s); return written; } int vstr_wrapper_vprintf(const char *format, va_list args) { Vstr_conf *conf; conf = get_vstr_conf(); if (conf) { return vstr_wrapper_vprintf_internal(conf, stdout, format, args); } return vprintf(format, args); } int vstr_wrapper_vfprintf(FILE *stream, const char *format, va_list args) { Vstr_conf *conf; conf = get_vstr_conf(); if (conf) { return vstr_wrapper_vprintf_internal(conf, stream, format, args); } return vfprintf(stream, format, args); } static inline int vstr_wrapper_vsnprintf_internal(char *str, size_t size, const char *format, va_list args) { Vstr_conf *conf; Vstr_base *s; int written; conf = get_vstr_conf(); if (conf) { s = vstr_make_base(conf); vstr_add_vfmt(s, 0, format, args); written = s->len; vstr_export_cstr_buf(s, 1, s->len, str, (size > 0) ? size : s->len + 1); vstr_free_base(s); return written; } return vsnprintf(str, size, format, args); } int vstr_wrapper_vsprintf(char *str, const char *format, va_list args) { return vstr_wrapper_vsnprintf_internal(str, 0, format, args); } int vstr_wrapper_vsnprintf(char *str, size_t size, const char *format, va_list args) { if (size > 0) { return vstr_wrapper_vsnprintf_internal(str, size, format, args); } return 0; } int vstr_wrapper_vasprintf(char **str, const char *format, va_list args) { size_t len = 100; int written; *str = malloc(len); while (TRUE) { va_list ac; va_copy(ac, args); written = vstr_wrapper_vsnprintf_internal(*str, len, format, ac); va_end(ac); if (written < len) { break; } len = written + 1; *str = realloc(*str, len); } return written; } METHOD(printf_hook_t, add_handler, void, private_printf_hook_t *this, char spec, printf_hook_function_t hook, ...) { int i = -1; bool failed = FALSE; printf_hook_handler_t *handler; printf_hook_argtype_t argtype; va_list args; if (SPEC_TO_INDEX(spec) <= -1 || SPEC_TO_INDEX(spec) >= countof(printf_hooks)) { DBG1(DBG_LIB, "'%c' is not a valid printf hook specifier, " "not registered!", spec); return; } INIT(handler, .hook = hook, ); va_start(args, hook); while (!failed) { argtype = va_arg(args, printf_hook_argtype_t); if (argtype == PRINTF_HOOK_ARGTYPE_END) { break; } if (++i >= ARGS_MAX) { DBG1(DBG_LIB, "Too many arguments for printf hook with " "specifier '%c', not registered!", spec); failed = TRUE; break; } switch (argtype) { case PRINTF_HOOK_ARGTYPE_INT: handler->argtypes[i] = VSTR_TYPE_FMT_INT; break; case PRINTF_HOOK_ARGTYPE_POINTER: handler->argtypes[i] = VSTR_TYPE_FMT_PTR_VOID; break; default: DBG1(DBG_LIB, "Invalid printf hook arg type for '%c'", spec); failed = TRUE; break; } } va_end(args); handler->numargs = i + 1; if (!failed && handler->numargs > 0) { Vstr_conf *conf = get_vstr_conf(); handler->name = malloc(2); handler->name[0] = spec; handler->name[1] = '\0'; vstr_fmt_add_handler(conf, handler); printf_hooks[SPEC_TO_INDEX(spec)] = handler; } else { free(handler); } } METHOD(printf_hook_t, destroy, void, private_printf_hook_t *this) { int i; Vstr_conf *conf; printf_hook_handler_t *handler; conf = get_vstr_conf(); for (i = 0; i < countof(printf_hooks); ++i) { handler = printf_hooks[i]; if (handler) { vstr_fmt_del(conf, handler->name); free(handler->name); free(handler); } } /* freeing the Vstr_conf of the main thread */ vstr_conf->destroy(vstr_conf); vstr_conf = NULL; vstr_exit(); free(this); } /* * see header file */ printf_hook_t *printf_hook_create() { private_printf_hook_t *this; INIT(this, .public = { .add_handler = _add_handler, .destroy = _destroy, }, ); memset(printf_hooks, 0, sizeof(printf_hooks)); if (!vstr_init()) { DBG1(DBG_LIB, "failed to initialize Vstr library!"); free(this); return NULL; } vstr_conf = thread_value_create((thread_cleanup_t)vstr_free_conf); return &this->public; }