ossp-pkg/l2/l2_channel.c
/*
** OSSP l2 - Flexible Logging
** Copyright (c) 2001-2005 Cable & Wireless <http://www.cw.com/>
** Copyright (c) 2001-2005 The OSSP Project <http://www.ossp.org/>
** Copyright (c) 2001-2005 Ralf S. Engelschall <rse@engelschall.com>
**
** This file is part of OSSP l2, a flexible logging library which
** can be found at http://www.ossp.org/pkg/lib/l2/.
**
** Permission to use, copy, modify, and distribute this software for
** any purpose with or without fee is hereby granted, provided that
** the above copyright notice and this permission notice appear in all
** copies.
**
** THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 AND COPYRIGHT HOLDERS AND THEIR
** 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.
**
** l2_channel.c: channel object
**
*/
#include "l2_p.h"
/*
* A channel is the central object for a logging stream. It is
* implemented by a framework (the code implemented here) which provides
* the channel API and which is equal for all channels and a particular
* handler which implements the characteristics specific to a particular
* channel class. The lifecycle of a channel is illustrated in the
* following figure:
*
* -----BEGIN EMBEDDED OBJECT-----
* Content-editor: xfig %s
* Content-encoding: gzip:9 base64
* Content-type: application/fig
* Content-viewer: xfig %s
* Description: L2 Channel Lifecycle
* Filename: l2_ch_lifecycle.fig
* Last-modified: 2000-09-28/14:40
* Name: l2_ch_lifecycle
* Version: eo/1.0
* H4sIAGdztDsCA62WTW/bMAyGz/WvENBzM5KyPnwusGFAdtp1l8BVWgOZUyTuhv77
* kfKHnC4t5CZJ7JAO3kcUKUq5/fr9m9IrKtab9uFYb55DcR/aLhyKH6E7NHWxDh17
* ShUIsAIofjbt4y4Ud1QgASgqQGlSt3V8Fai0AoV8gTJyI76xLD6Tb35iPSpybNlS
* PsknV5po5WC0BZZRWSlL8km+tlYsa3MwJfAP6Kdokl8iltHKwjiwJ5jJL52DbIxB
* Z+aY5BvSVT7GEieSzISZfGN9Fa0cjAVj55iZz7XPxxg6mdTMNz5/Ug55ncwwyXdU
* mnyMPS148p1bUHCPAPPcJN+jLOrM3GjUOMckX2PV16ygiJDL9Zi7Qa4GR36i4kYM
* XMW6yZ1LJP160y+iofqUy4SeKW01zCtaMT2XBjSRUrdFJr1h0rmAZg3qhnV0cUAT
* KTVcfoYsjOmN1jLxbPWJdZV6T2GkTstPb2pOh3ilek+kNN3LmWO6UuflZB0/YvYk
* B+eYXpi4PM4YYs80g3XK/Gh1JHG0UC/p3OFIhRkhiuXNJ3aaDr0/tKExbraWqtFX
* g1qsiyue8qDxWq0ykdI+l5/g2eE87rCX79XGj8cK6CGgMh0gyH8qYPrPoTCeGzx0
* Jft6yUHU+3bbPL4cwi8AzFAazU+XKXlJlHL6O64uec3KQ9h0OQPK1uJRZNob9RCO
* 3WH/mjGc5kC1HXX759Bmiixas0jkZHIgBy9wrH8PTRe+bHcvx6dMrTUOPqOVYA1x
* sFhJQnf7Y8hUOVfCOZV/v3h+3N88W7tmG7rm9ztCXPGE/Gw4IuAgLUd67M4V3f8/
* nPS/M9IprN/UXfMnR2b8MA4Rl6Np3wh9EtJM6OWRlkGrfrtUa1L3T5u2DTu15qnW
* r/Wup/wDvlAM/PkMAAA=
* -----END EMBEDDED OBJECT-----
*/
/* create channel */
l2_result_t l2_channel_create(l2_channel_t **chp, l2_env_t *env, const char *name)
{
l2_channel_t *ch;
l2_handler_t *h;
int i;
/* argument sanity check */
if (env == NULL || name == NULL)
return L2_ERR_ARG;
/* lookup channel handler */
h = NULL;
for (i = 0; i < L2_MAX_HANDLERS && env->handlers[i] != NULL; i++) {
if (strcmp(env->handlers[i]->name, name) == 0) {
h = env->handlers[i];
break;
}
}
if (h == NULL)
return L2_ERR_CH;
/* allocate channel structure */
if ((ch = (l2_channel_t *)malloc(sizeof(l2_channel_t))) == NULL)
return L2_ERR_SYS;
/* initialize channel structure */
ch->env = env;
ch->state = L2_CHSTATE_CREATED;
ch->parent = NULL;
ch->sibling = NULL;
ch->child = NULL;
memset(&ch->context, 0, sizeof(l2_context_t));
memcpy(&ch->handler, h, sizeof(l2_handler_t));
ch->levelmask = env->levelmask;
ch->flushmask = env->flushmask;
/* (optionally) perform create operation in handler */
if (ch->handler.create != NULL) {
if (ch->handler.create(&ch->context, ch) != L2_OK) {
free(ch);
return L2_ERR_SYS;
}
}
/* pass object to caller */
(*chp) = ch;
return L2_OK;
}
/* link channels */
l2_result_t l2_channel_link(l2_channel_t *ch0, l2_link_t id, l2_channel_t *ch, ...)
{
l2_channel_t *chT;
l2_channel_t *chN;
va_list ap;
/* argument sanity check */
if (ch0 == NULL || ch == NULL)
return L2_ERR_ARG;
/* perform either child or sibling linking operation(s) */
if (id == L2_LINK_CHILD) {
/* make sure child parents are filters only */
if (ch0->handler.type != L2_CHANNEL_FILTER)
return L2_ERR_USE;
va_start(ap, ch);
chT = ch;
do {
chN = (l2_channel_t *)va_arg(ap, l2_channel_t *);
if (chN != NULL && chT->handler.type != L2_CHANNEL_FILTER)
return L2_ERR_USE;
} while ((chT = chN) != NULL);
va_end(ap);
/* perform link operation(s) */
va_start(ap, ch);
do {
ch->parent = ch0;
if (ch0->child == NULL)
ch0->child = ch;
else {
chT = ch0->child;
while (chT->sibling != NULL)
chT = chT->sibling;
chT->sibling = ch;
}
ch0 = ch;
ch = (l2_channel_t *)va_arg(ap, l2_channel_t *);
} while (ch != NULL);
va_end(ap);
}
else if (id == L2_LINK_SIBLING) {
/* perform link operation(s) */
va_start(ap, ch);
do {
ch0->sibling = ch;
ch->parent = ch0->parent;
ch0 = ch;
ch = (l2_channel_t *)va_arg(ap, l2_channel_t *);
} while (ch != NULL);
va_end(ap);
}
return L2_OK;
}
/* unlink channels */
l2_result_t l2_channel_unlink(l2_channel_t *ch)
{
l2_channel_t *chS;
l2_channel_t *chP;
/* argument sanity check */
if (ch == NULL)
return L2_ERR_ARG;
/* make sure channel is in state "created" */
if (ch->state != L2_CHSTATE_CREATED)
return L2_ERR_USE;
/* make sure channel has no childs */
if (ch->child != NULL)
return L2_ERR_USE;
/* unlink the channel */
chP = ch->parent;
ch->parent = NULL;
if (chP != NULL) {
if (chP->child == ch)
chP->child = ch->sibling;
else {
chS = chP->child;
while (chS->sibling != ch)
chS = chS->sibling;
chS->sibling = ch->sibling;
}
}
return L2_OK;
}
/* return upstream channel */
l2_result_t l2_channel_upstream(l2_channel_t *ch, l2_channel_t **chU)
{
/* argument sanity check */
if (ch == NULL || chU == NULL)
return L2_ERR_ARG;
/* determine parent/upstream channel */
*chU = ch->parent;
return (*chU != NULL ? L2_OK : L2_ERR_CH);
}
/* return (subsequent) downstream channel(s) */
l2_result_t l2_channel_downstream(l2_channel_t *ch, l2_channel_t **chD)
{
/* argument sanity check */
if (ch == NULL || chD == NULL)
return L2_ERR_ARG;
/* determine (next) downstream/child channel */
if (*chD == NULL)
*chD = ch->child;
else
*chD = (*chD)->sibling;
return (*chD != NULL ? L2_OK : L2_ERR_CH);
}
/* return channel type */
l2_result_t l2_channel_type(l2_channel_t *ch, l2_chtype_t *type)
{
/* argument sanity check */
if (ch == NULL || type == NULL)
return L2_ERR_ARG;
/* return type */
(*type) = ch->handler.type;
return L2_OK;
}
/* set channel level masks */
l2_result_t l2_channel_levels(l2_channel_t *ch, unsigned int levelmask, unsigned int flushmask)
{
/* argument sanity check */
if (ch == NULL)
return L2_ERR_ARG;
/* override global level mask */
ch->levelmask = levelmask;
ch->flushmask = flushmask;
return L2_OK;
}
/* configure channel */
l2_result_t l2_channel_configure(l2_channel_t *ch, const char *fmt, ...)
{
l2_result_t rv;
va_list ap;
/* argument sanity check */
if (ch == NULL || fmt == NULL)
return L2_ERR_ARG;
/* make sure the channel is in state "created" */
if (ch->state != L2_CHSTATE_CREATED)
return L2_ERR_USE;
/* pass operation to handler */
rv = L2_OK;
va_start(ap, fmt);
if (ch->handler.configure != NULL)
rv = ch->handler.configure(&ch->context, ch, fmt, ap);
va_end(ap);
return rv;
}
/* open channel */
l2_result_t l2_channel_open(l2_channel_t *ch)
{
l2_result_t rv;
l2_result_t rvD;
l2_channel_t *chD;
/* argument sanity check */
if (ch == NULL)
return L2_ERR_ARG;
/* make sure channel is in state "created" */
if (ch->state != L2_CHSTATE_CREATED)
return L2_ERR_USE;
/* perform operation */
if (ch->handler.open != NULL)
rv = ch->handler.open(&ch->context, ch);
else
rv = L2_OK_PASS;
/* optionally pass operation downstream */
if (rv == L2_OK_PASS) {
rv = L2_OK;
chD = NULL;
while (l2_channel_downstream(ch, &chD) == L2_OK)
if ((rvD = l2_channel_open(chD)) != L2_OK)
rv = rvD;
if (rv != L2_OK) {
chD = NULL;
while (l2_channel_downstream(ch, &chD) == L2_OK)
l2_channel_close(chD);
}
}
/* mark channel as opened */
if (rv == L2_OK)
ch->state = L2_CHSTATE_OPENED;
return rv;
}
/* write to channel */
l2_result_t l2_channel_write(l2_channel_t *ch, l2_level_t level, const char *buf, size_t bufsize)
{
int l, j;
l2_result_t rv;
l2_result_t rvD;
l2_channel_t *chD;
/* argument sanity check */
if (ch == NULL || level == 0 || buf == NULL)
return L2_ERR_ARG;
/* make sure channel is in state "opened" */
if (ch->state != L2_CHSTATE_OPENED)
return L2_ERR_USE;
/* make sure only a single level is specified */
for (l = level, j = 0; l != 0; l = (l >> 1))
if (l & 0x1)
j++;
if (j != 1)
return L2_ERR_ARG;
/* check whether level mask already stops processing */
if (!(ch->levelmask & level))
return L2_OK;
/* short circuiting */
if (bufsize == 0)
return L2_OK;
/* perform operation */
if (ch->handler.write != NULL)
rv = ch->handler.write(&ch->context, ch, level, buf, bufsize);
else
rv = L2_OK_PASS;
/* optionally pass operation downstream */
if (rv == L2_OK_PASS) {
rv = L2_OK;
chD = NULL;
while (l2_channel_downstream(ch, &chD) == L2_OK)
if ((rvD = l2_channel_write(chD, level, buf, bufsize)) != L2_OK)
rv = rvD;
}
return rv;
}
/* flush channel (stack) */
l2_result_t l2_channel_flush(l2_channel_t *ch)
{
l2_result_t rv;
l2_result_t rvD;
l2_channel_t *chD;
/* argument sanity check */
if (ch == NULL)
return L2_ERR_ARG;
/* make sure channel is in state "opened" */
if (ch->state != L2_CHSTATE_OPENED)
return L2_ERR_USE;
/* perform operation */
if (ch->handler.flush != NULL)
rv = ch->handler.flush(&ch->context, ch);
else
rv = L2_OK_PASS;
/* optionally pass operation downstream */
if (rv == L2_OK_PASS) {
rv = L2_OK;
chD = NULL;
while (l2_channel_downstream(ch, &chD) == L2_OK)
if ((rvD = l2_channel_flush(chD)) != L2_OK)
rv = rvD;
}
return rv;
}
/* close channel (stack) */
l2_result_t l2_channel_close(l2_channel_t *ch)
{
l2_result_t rv;
l2_result_t rvD;
l2_channel_t *chD;
/* argument sanity check */
if (ch == NULL)
return L2_ERR_ARG;
/* make sure channel is in state "opened" */
if (ch->state != L2_CHSTATE_OPENED)
return L2_ERR_USE;
/* perform operation */
if (ch->handler.close != NULL)
rv = ch->handler.close(&ch->context, ch);
else
rv = L2_OK_PASS;
/* optionally pass operation downstream */
if (rv == L2_OK_PASS) {
rv = L2_OK;
chD = NULL;
while (l2_channel_downstream(ch, &chD) == L2_OK)
if ((rvD = l2_channel_close(chD)) != L2_OK)
rv = rvD;
}
/* mark channel as closed */
if (rv == L2_OK)
ch->state = L2_CHSTATE_CREATED;
return rv;
}
/* destroy channel */
l2_result_t l2_channel_destroy(l2_channel_t *ch)
{
l2_result_t rv;
l2_result_t rvD; /* downstream */
l2_channel_t *chD;
l2_result_t rvL; /* lookahead */
l2_channel_t *chL;
/* argument sanity check */
if (ch == NULL)
return L2_ERR_ARG;
/* make sure channel is in state "opened" */
if (ch->state == L2_CHSTATE_OPENED)
if ((rv = l2_channel_close(ch)) != L2_OK)
return rv;
/* perform operation */
if (ch->handler.destroy != NULL)
rv = ch->handler.destroy(&ch->context, ch);
else
rv = L2_OK_PASS;
/* optionally pass operation downstream */
if (rv == L2_OK_PASS) {
rv = L2_OK;
chD = NULL;
if (l2_channel_downstream(ch, &chD) == L2_OK) {
chL = chD;
do {
rvL = l2_channel_downstream(ch, &chL);
if ((rvD = l2_channel_destroy(chD)) != L2_OK)
rv = rvD;
if (rvL == L2_OK)
chD = chL;
} while ((rv == L2_OK) && (rvL == L2_OK));
}
}
/* free channel structure */
if (rv == L2_OK)
free(ch);
return rv;
}
/* log a message to channel */
l2_result_t l2_channel_log(l2_channel_t *ch, l2_level_t level, const char *fmt, ...)
{
va_list ap;
l2_result_t rv;
/* pass-through to va_list-based variant */
va_start(ap, fmt);
rv = l2_channel_vlog(ch, level, fmt, ap);
va_end(ap);
return rv;
}
/* indirect callback function from l2_channel_vlog for flushing */
static int l2_channel_vlog_flush(l2_util_format_t *vfmt)
{
/* we do no format buffer flushing */
return -1;
}
/* indirect callback function from l2_channel_vlog for formatting */
static void l2_channel_vlog_format(
l2_util_format_t *vfmt,
char *cPrefix, char *cPad, char **cppOut, size_t *npOutLen,
char *cpBuf, int nBufLenMax, char *cpParam, char cId, va_list *apArgs)
{
l2_env_t *env = (l2_env_t *)(vfmt->data[0].vp);
l2_result_t rv;
int i;
/* init formatting result */
*cPrefix = '\0';
*cPad = ' ';
*cppOut = NULL;
*npOutLen = 0;
/* iterate over all configured L2 formatters */
for (i = 0; i < L2_MAX_FORMATTERS && env->formatters[i].cb != NULL; i++) {
if (env->formatters[i].id == cId) {
rv = env->formatters[i].cb(env->formatters[i].ctx, cId, cpParam,
cpBuf, nBufLenMax, npOutLen, apArgs);
vfmt->data[1].i = (int)rv;
if (rv == L2_OK) {
*cppOut = cpBuf;
break;
}
}
}
return;
}
/* log a message to channel (va_list-variant) */
l2_result_t l2_channel_vlog(l2_channel_t *ch, l2_level_t level, const char *fmt, va_list ap)
{
int l, j;
size_t len;
l2_result_t rv;
l2_util_format_t vfmt;
l2_env_t *env;
/* argument sanity check */
if (ch == NULL || level == 0 || fmt == NULL)
return L2_ERR_ARG;
/* make sure only a single level is specified */
for (l = level, j = 0; l != 0; l = (l >> 1))
if (l & 0x1)
j++;
if (j != 1)
return L2_ERR_ARG;
/* check whether level mask already stops processing */
if (!(ch->levelmask & level))
return L2_OK;
/* format message */
env = ch->env;
vfmt.curpos = env->message;
vfmt.endpos = env->message + L2_MAX_MSGSIZE;
vfmt.data[0].vp = env;
vfmt.data[1].i = L2_ERR_FMT;
vfmt.flush = l2_channel_vlog_flush;
vfmt.format = l2_channel_vlog_format;
len = l2_util_format(&vfmt, fmt, ap);
/* check for formatting error including buffer overrun */
if (len == -1)
return (l2_result_t)(vfmt.data[1].i);
/* check for formatting led to completely empty message */
if (len == 0)
return L2_ERR_FMT;
/* check for formatting led to newline-only message */
if (len == 1 && env->message[len] == '\n')
return L2_ERR_FMT;
/* make sure a trailing newline exists; L2_MSG_BUFSIZE has room for CR/LF */
if (env->message[len-1] != '\n')
env->message[len++] = '\n';
/* make sure a trailing NUL exists; L2_MSG_BUFSIZE has room for NUL */
env->message[len] = '\0';
/* write message to channel */
rv = L2_OK;
if ((rv = l2_channel_write(ch, level, env->message, len)) != L2_OK)
return rv;
if (ch->flushmask & level)
l2_channel_flush(ch);
return rv;
}
/* return environment object */
l2_result_t l2_channel_env(l2_channel_t *ch, l2_env_t **env)
{
if (ch == NULL || env == NULL)
return L2_ERR_ARG;
*env = ch->env;
return L2_OK;
}