/* ** OSSP l2 - Flexible Logging ** Copyright (c) 2001-2005 Cable & Wireless ** Copyright (c) 2001-2005 The OSSP Project ** Copyright (c) 2001-2005 Ralf S. Engelschall ** ** 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; }