/*
* 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 <http://www.fsf.org/copyleft/gpl.txt>.
*
* 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 <utils/utils.h>
#include <utils/debug.h>
#include <threading/thread_value.h>
#include <vstr.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
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;
}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>