head 1.35; access; symbols L2_0_9_13:1.35 FSL_1_7_0:1.35 L2_0_9_12:1.35 LMTP2NNTP_1_4_1:1.35 LMTP2NNTP_1_4_0:1.35 FSL_1_6_1:1.35 L2_0_9_11:1.35 FSL_1_6_0:1.34 FSL_1_6b2:1.34 L2_0_9_10:1.34 FSL_1_6b1:1.34 L2_0_9_9:1.34 LMTP2NNTP_1_3_0:1.33 LMTP2NNTP_1_3b2:1.33 LMTP2NNTP_1_3b1:1.33 LMTP2NNTP_1_3a3:1.33 FSL_1_5_0:1.33 LMTP2NNTP_1_3a2:1.33 FSL_1_5a3:1.33 LMTP2NNTP_1_3a1:1.33 FSL_1_5a2:1.33 L2_0_9_8:1.33 FSL_1_5a1:1.33 L2_0_9_7:1.33 L2_0_9_6:1.33 FSL_1_4_0:1.33 FSL_1_4b1:1.33 L2_0_9_5:1.33 FSL_1_4a1:1.32 FSL_1_3_0:1.32 FSL_1_3b1:1.32 L2_0_9_4:1.32 FSL_1_2_1:1.32 L2_0_9_3:1.32 FSL_1_2_0:1.32 L2_0_9_2:1.32 FSL_1_1_0:1.32 FSL_1_1b1:1.32 WORKOFF:1.32.0.2 WORKOFF_BP:1.32 FSL_1_0_8:1.32 LMTP2NNTP_1_2_0:1.31 LMTP2NNTP_1_2b4:1.31 LMTP2NNTP_1_2b3:1.31 LMTP2NNTP_1_2b2:1.31 LMTP2NNTP_1_2b1:1.31 LMTP2NNTP_1_2a8:1.31 LMTP2NNTP_1_2a7:1.31 FSL_1_0_7:1.31 FSL_1_0_6:1.29 FSL_1_0_5:1.29 FSL_1_0_4:1.29 L2_0_9_1:1.29 FSL_1_0_3:1.29 LMTP2NNTP_1_2a6:1.29 FSL_1_0_2:1.29 FSL_1_0_1:1.29 FSL_1_0_0:1.29 FSL_0_9_0:1.29 L2_0_9_0:1.29 FSL_0_1_12:1.28 FSL_0_1_11:1.28 FSL_0_1_10:1.28 FSL_0_1_9:1.28 FSL_0_1_8:1.28 FSL_0_1_7:1.28 FSL_0_1_6:1.28 FSL_0_1_5:1.28 FSL_0_1_1:1.27 LMTP2NNTP_1_2a5:1.27 LMTP2NNTP_1_2a4:1.27 LMTP2NNTP_1_2a3:1.27 LMTP2NNTP_1_2a1:1.26 LMTP2NNTP_1_1_1:1.20 LMTP2NNTP_1_1_0:1.20 LMTP2NNTP_1_1b4:1.20 LMTP2NNTP_1_1b3:1.20 L2_CHANNEL_ONLY_REVAMPING_BEFORE:1.20 LMTP2NNTP_1_1b2:1.20 LMTP2NNTP_1_1b1:1.20 L2_0_1_0:1.18 L2NGATE:1.8.0.2 START_MICHAEL:1.5 L2_INITIAL:1.1.1.1 OSSP:1.1.1; locks; strict; comment @ * @; 1.35 date 2005.10.03.08.08.11; author rse; state Exp; branches; next 1.34; 1.34 date 2005.01.24.15.03.17; author rse; state Exp; branches; next 1.33; 1.33 date 2003.11.06.15.31.47; author thl; state Exp; branches; next 1.32; 1.32 date 2003.02.13.15.37.28; author rse; state Exp; branches; next 1.31; 1.31 date 2003.01.27.16.01.35; author thl; state Exp; branches; next 1.30; 1.30 date 2003.01.06.11.41.51; author rse; state Exp; branches; next 1.29; 1.29 date 2002.07.30.19.08.25; author rse; state Exp; branches; next 1.28; 1.28 date 2002.07.24.09.36.54; author rse; state Exp; branches; next 1.27; 1.27 date 2002.01.02.17.07.38; author rse; state Exp; branches; next 1.26; 1.26 date 2001.11.07.16.04.22; author rse; state Exp; branches; next 1.25; 1.25 date 2001.11.07.11.37.18; author rse; state Exp; branches; next 1.24; 1.24 date 2001.11.06.15.02.49; author rse; state Exp; branches; next 1.23; 1.23 date 2001.11.04.13.55.06; author rse; state Exp; branches; next 1.22; 1.22 date 2001.11.04.13.21.17; author rse; state Exp; branches; next 1.21; 1.21 date 2001.11.03.22.51.36; author rse; state Exp; branches; next 1.20; 1.20 date 2001.09.27.13.56.35; author rse; state Exp; branches; next 1.19; 1.19 date 2001.09.24.15.36.23; author rse; state Exp; branches; next 1.18; 1.18 date 2001.09.13.12.50.26; author thl; state Exp; branches; next 1.17; 1.17 date 2001.09.12.09.42.34; author ms; state Exp; branches; next 1.16; 1.16 date 2001.09.06.14.37.53; author rse; state Exp; branches; next 1.15; 1.15 date 2001.09.05.19.58.44; author rse; state Exp; branches; next 1.14; 1.14 date 2001.09.05.13.56.12; author rse; state Exp; branches; next 1.13; 1.13 date 2001.09.05.10.59.37; author ms; state Exp; branches; next 1.12; 1.12 date 2001.09.04.15.41.17; author rse; state Exp; branches; next 1.11; 1.11 date 2001.09.04.13.52.59; author rse; state Exp; branches; next 1.10; 1.10 date 2001.09.03.13.43.33; author rse; state Exp; branches; next 1.9; 1.9 date 2001.09.03.11.50.25; author rse; state Exp; branches; next 1.8; 1.8 date 2001.09.02.15.35.40; author ms; state Exp; branches; next 1.7; 1.7 date 2001.09.02.14.37.58; author ms; state Exp; branches; next 1.6; 1.6 date 2001.08.26.13.04.37; author ms; state Exp; branches; next 1.5; 1.5 date 2001.08.15.10.36.03; author rse; state Exp; branches; next 1.4; 1.4 date 2001.05.24.09.40.28; author rse; state Exp; branches; next 1.3; 1.3 date 2001.05.22.20.00.12; author rse; state Exp; branches; next 1.2; 1.2 date 2001.05.22.18.47.31; author rse; state Exp; branches; next 1.1; 1.1 date 2001.05.10.19.46.01; author rse; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2001.05.10.19.46.01; author rse; state Exp; branches; next ; desc @@ 1.35 log @Adjust copyright messages for new year 2005. @ text @/* ** 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; } @ 1.34 log @Adjust copyright messages for new year 2005. @ text @d3 3 a5 3 ** Copyright (c) 2001-2004 Cable & Wireless ** Copyright (c) 2001-2004 The OSSP Project ** Copyright (c) 2001-2004 Ralf S. Engelschall @ 1.33 log @fix PR#23: l2 does not terminate a message when it contains a newline @ text @d3 3 a5 3 ** Copyright (c) 2001-2003 Cable & Wireless Deutschland GmbH ** Copyright (c) 2001-2003 The OSSP Project (http://www.ossp.org/) ** Copyright (c) 2001-2003 Ralf S. Engelschall @ 1.32 log @Remove NULL checks for va_list based variables because it is not portable to assume that va_list behaves like a pointer or other scalar type. Indeed it is a full structure on some platforms like FreeBSD/alpha. @ text @d585 4 a588 1 if ((len = l2_util_format(&vfmt, fmt, ap)) == -1) d590 2 d595 6 a600 4 /* make sure a trailing newline exists */ if (env->message[len-1] != '\n') { if (len == L2_MAX_MSGSIZE) return L2_ERR_MEM; d602 3 a604 2 env->message[len] = '\0'; } @ 1.31 log @fix iteration through non malloc(3)ed memory in l2_channel_destroy(). Bug caught on FreeBSD5 @ text @d563 1 a563 1 if (ch == NULL || level == 0 || fmt == NULL || ap == NULL) @ 1.30 log @- remove trailing whitespaces - adjust copyright messages - consistently use "OSSP l2" - consistently talk about "Flexible Logging" - use standard OSSP ASCII-art @ text @d458 1 a458 1 l2_result_t rvD; d460 2 d482 10 a491 3 while (l2_channel_downstream(ch, &chD) == L2_OK) if ((rvD = l2_channel_destroy(chD)) != L2_OK) rv = rvD; @ 1.29 log @polish for release @ text @d2 4 a5 3 ** OSSP l2 - Logging Library ** Copyright (c) 2001-2002 The OSSP Project (http://www.ossp.org/) ** Copyright (c) 2001-2002 Cable & Wireless Deutschland (http://www.cw.com/de/) d7 1 a7 1 ** This file is part of OSSP L2, a flexible logging library which d34 1 a34 1 /* d313 2 a314 2 /* optionally pass operation downstream */ d371 2 a372 2 /* optionally pass operation downstream */ d404 2 a405 2 /* optionally pass operation downstream */ d427 1 a427 1 d437 2 a438 2 /* optionally pass operation downstream */ d475 2 a476 2 /* optionally pass operation downstream */ d532 1 a532 1 rv = env->formatters[i].cb(env->formatters[i].ctx, cId, cpParam, @ 1.28 log @make sure the channel mask on each individual channel is also active @ text @d2 1 a2 1 ** L2 - OSSP Logging Library d7 1 a7 1 ** can be found at http://www.ossp.org/pkg/l2/. @ 1.27 log @bump copyright year @ text @d337 1 d349 11 @ 1.26 log @do not re-init related links, because the parser else has no chance @ text @d3 2 a4 2 ** Copyright (c) 2001 The OSSP Project (http://www.ossp.org/) ** Copyright (c) 2001 Cable & Wireless Deutschland (http://www.cw.com/de/) @ 1.25 log @More preparations for forthcoming channel tree specification parser (especially to allow the parser to determine the handler structure from a handler name without introducing another and this way redundant sub-API): - add "char *name" to l2_handler_t in order to tag each handler structure with the corresponding channel name - add l2_env_handler() function to add handler to l2_env_t objects. All l2_handler_xxxx are automatically pre-configured there after l2_env_create(). - change l2_channel_create() to take a "const char *name" (handler name) instead of the "l2_handler_t *h" (handler pointer) to make the stuff consistent and more clear. - adjust l2_test.c to reflect the changes. @ text @a148 2 ch->sibling = NULL; ch->child = NULL; a167 2 ch->sibling = NULL; ch->child = NULL; @ 1.24 log @Change semantics of sibling linkage operations in order to fulfill the requirements in the forthcoming channel tree specification parser. @ text @d70 1 a70 1 l2_result_t l2_channel_create(l2_channel_t **chp, l2_env_t *env, l2_handler_t *h) d73 2 d77 1 a77 1 if (h == NULL || env == NULL) d79 11 @ 1.23 log @code cleanups @ text @d108 1 a108 1 l2_result_t l2_channel_link(l2_channel_t *chR, l2_link_t id, l2_channel_t *chC, ...) d115 1 a115 1 if (chR == NULL || chC == NULL) d118 10 a127 15 /* make sure root channel is in state "created" */ if (chR->state != L2_CHSTATE_CREATED) return L2_ERR_USE; /* make sure root channel is a filter channel */ if (chR->handler.type != L2_CHANNEL_FILTER) return L2_ERR_USE; /* check childs */ va_start(ap, chC); chT = chC; do { chN = (l2_channel_t *)va_arg(ap, l2_channel_t *); if (id == L2_LINK_CHILDS && chN != NULL) if (chT->handler.type != L2_CHANNEL_FILTER) d129 2 a130 2 } while ((chT = chN) != NULL); va_end(ap); d132 31 a162 17 /* perform link operation(s) */ va_start(ap, chC); do { chC->parent = chR; chC->sibling = NULL; chC->child = NULL; if (chR->child == NULL) chR->child = chC; else { chT = chR->child; while (chT->sibling != NULL) chT = chT->sibling; chT->sibling = chC; } if (id == L2_LINK_CHILDS) chR = chC; chC = (l2_channel_t *)va_arg(ap, l2_channel_t *); a163 2 while (chC != NULL); va_end(ap); @ 1.22 log @Channel-Only Revamping Step 2: - moved code of l2_stream.c into (new) l2_env.c and l2_channel.c - created new l2_env_t and l2_env_xxx() - changed l2_xx_create() functions to also return l2_result_t - moved error handling into l2_env_t - replaced l2_channel_stack() with two new and more flexible l2_channel_link() and l2_channel_unlink() functions - rewritten test stuff in l2_test.c to use new structure - added new l2_channel_env() function for retriving l2_env_t Puhhh.... @ text @d27 1 a27 1 ** l2_channel.c: channel handling a30 1 #include "l2.h" a31 2 #include @ 1.21 log @Channel-Only Revamping Step 1: allow multiple downstream channels in order to approach the later tree-like channel-only structure. @ text @d73 1 a73 1 l2_channel_t *l2_channel_create(l2_handler_t *h) d78 2 a79 2 if (h == NULL) return NULL; d83 1 a83 1 return NULL; d86 1 d93 2 a94 5 ch->rvErrorInfo = L2_OK; ch->szErrorInfo[0] = '\0'; ch->szError[0] = '\0'; ch->levelmask = L2_LEVEL_ALL; ch->flushmask = L2_LEVEL_NONE; d100 1 a100 1 return NULL; d104 4 a107 1 return ch; d110 2 a111 2 /* stack channel on top of another channel */ l2_result_t l2_channel_stack(l2_channel_t *ch, l2_channel_t *chP) d114 2 d118 1 a118 1 if (ch == NULL || chP == NULL) d121 2 a122 3 /* make sure both channels are in state "created" */ if ( ch->state != L2_CHSTATE_CREATED || chP->state != L2_CHSTATE_CREATED) d125 2 a126 2 /* make sure parent channel is a filter channel */ if (chP->handler.type != L2_CHANNEL_FILTER) d129 47 a175 2 /* make sure child still has no parent */ if (ch->parent != NULL) d178 16 a193 9 /* stack the two channels */ ch->parent = chP; if (chP->child == NULL) chP->child = ch; else { chT = chP->child; while (chT->sibling != NULL) chT = chT->sibling; chT->sibling = ch; d241 14 d466 2 a467 1 l2_result_t l2_channel_errorinfo(l2_channel_t *ch, l2_result_t rv, const char *fmt, ...) d470 1 d472 1 a472 5 /* argument sanity check */ if (ch == NULL || rv == L2_OK || fmt == NULL) return L2_ERR_ARG; /* remember error information */ d474 1 a474 2 l2_util_vsprintf(ch->szErrorInfo, sizeof(ch->szErrorInfo), fmt, ap); ch->rvErrorInfo = rv; d477 39 a515 1 return L2_OK; d518 2 a519 1 char *l2_channel_strerror(l2_channel_t *ch, l2_result_t rv) d521 5 a525 4 char *sz; char *cpBuf; int nBuf; int n; d528 13 a540 2 if (ch == NULL) return NULL; d542 19 a560 23 /* start at begin of buffer */ cpBuf = ch->szError; nBuf = sizeof(ch->szError); /* translate result value into corresponding string */ if (rv == L2_OK) sz = "everything ok"; else if (rv == L2_ERR_ARG) sz = "invalid argument"; else if (rv == L2_ERR_USE) sz = "invalid use"; else if (rv == L2_ERR_MEM) sz = "no more memory available"; else if (rv == L2_ERR_SYS) sz = "operating system error"; else if (rv == L2_ERR_IO) sz = "input/output error"; else if (rv == L2_ERR_FMT) sz = "formatting error"; else if (rv == L2_ERR_INT) sz = "internal error"; else sz = "unknown error"; n = l2_util_sprintf(cpBuf, nBuf, "%s", sz); cpBuf += n; nBuf -= n; /* optionally annotate with error information */ if (rv == ch->rvErrorInfo && ch->szErrorInfo[0] != '\0') { n = l2_util_sprintf(cpBuf, nBuf, "; %s", ch->szErrorInfo); cpBuf += n; nBuf -= n; d563 9 a571 6 /* optionally annotate with operating system error information */ if (rv == L2_ERR_SYS) { n = l2_util_sprintf(cpBuf, nBuf, "; %s (%d)", strerror(errno), errno); cpBuf += n; nBuf -= n; } d573 7 a579 2 /* return pointer to internal buffer */ return ch->szError; @ 1.20 log @document channel lifecycle @ text @d54 1 a54 1 * H4sIAIsvszsCA62WTW/bMAyGz/WvENBzM5KyPnwusGFAdtp1l8BVWgOZUyTuhv77 d87 3 a89 1 ch->downstream = NULL; d95 2 d109 78 d211 1 a211 1 /* open channel (stack) */ d215 2 d226 18 a243 4 /* skip empty open handlers on channel stack */ while (ch != NULL && ch->handler.open == NULL) { ch->state = L2_CHSTATE_OPENED; ch = ch->downstream; a244 2 if (ch == NULL) return L2_ERR_USE; d246 1 a246 2 /* pass operation to handler */ rv = ch->handler.open(&ch->context, ch); d253 1 a253 1 /* write to channel (stack) */ d257 2 d261 1 a261 1 if (ch == NULL || buf == NULL) d272 14 a285 8 /* walk to next available write handler */ while (ch != NULL && ch->handler.write == NULL) ch = ch->downstream; if (ch == NULL) return L2_ERR_USE; /* pass operation to handler */ rv = ch->handler.write(&ch->context, ch, level, buf, bufsize); d294 2 d305 14 a318 8 /* walk to next available flush handler */ while (ch != NULL && ch->handler.flush == NULL) ch = ch->downstream; if (ch == NULL) return L2_ERR_USE; /* pass operation to handler */ rv = ch->handler.flush(&ch->context, ch); d327 2 d338 13 a350 4 /* walk to next available close handler */ while (ch != NULL && ch->handler.close == NULL) { ch->state = L2_CHSTATE_CREATED; ch = ch->downstream; a351 2 if (ch == NULL) return L2_ERR_USE; d353 1 a353 2 /* pass operation to handler */ rv = ch->handler.close(&ch->context, ch); d364 2 d376 2 a377 6 /* walk to next available destroy handler */ while (ch != NULL && ch->handler.destroy == NULL) ch = ch->downstream; /* pass operation to handler */ if (ch != NULL) d380 4 d385 5 d392 2 a393 1 free(ch); a459 42 } /* stack channel on top of another channel */ l2_result_t l2_channel_stack(l2_channel_t *ch, l2_channel_t *chTop) { /* argument sanity check */ if (ch == NULL || chTop == NULL) return L2_ERR_ARG; /* make sure both channels are in state "created" */ if ( ch->state != L2_CHSTATE_CREATED || chTop->state != L2_CHSTATE_CREATED) return L2_ERR_USE; /* make sure top channel is a filter channel */ if (chTop->handler.type != L2_CHANNEL_FILTER) return L2_ERR_USE; /* stack the channels */ chTop->downstream = ch; return L2_OK; } /* return downstream channel */ l2_channel_t *l2_channel_downstream(l2_channel_t *ch) { /* argument sanity check */ if (ch == NULL) return NULL; return ch->downstream; } /* return channel type */ l2_chtype_t l2_channel_type(l2_channel_t *ch) { /* argument sanity check */ if (ch == NULL) return L2_CHANNEL_FILTER; /* FIXME */ return ch->handler.type; @ 1.19 log @Provide convinient channel error handling functions. @ text @d36 36 @ 1.18 log @temporary bugfix @ text @d54 3 d233 64 @ 1.17 log @Upgraded both stream and channel-level APIs to include new L2_LEVEL parameter design. @ text @d269 1 a269 1 return NULL; @ 1.16 log @Replace generic L2_ERROR with more granular L2_ERR_XXX and make sure that we always check with "!= L2_OK". @ text @d120 1 a120 1 l2_result_t l2_channel_write(l2_channel_t *ch, const char *buf, size_t bufsize) d143 1 a143 1 rv = ch->handler.write(&ch->context, ch, buf, bufsize); @ 1.15 log @Perform the channel state tagging immediately for a channel and not afterwards. Functionality-wise this is the same, but semantically it is more clean. @ text @d36 1 d41 1 d44 2 d48 2 d54 7 a60 3 if (ch->handler.create(&ch->context, ch) == L2_ERROR) { free(ch); return NULL; d62 1 d66 1 d72 1 d74 3 a76 1 return L2_ERROR; d78 4 a81 1 return L2_ERROR; d83 2 a84 1 rv = ch->handler.configure(&ch->context, ch, fmt, ap); d86 1 d90 1 d95 1 d97 3 a99 1 return L2_ERROR; d101 3 a103 1 return L2_ERROR; d109 3 a111 1 return L2_ERROR; d115 1 d119 1 d124 5 a128 2 if (ch == NULL) return L2_ERROR; d130 3 a132 3 return L2_ERROR; if (buf == NULL) return L2_ERROR; d135 2 d140 3 a142 1 return L2_ERROR; d144 1 d148 1 d153 1 d155 3 a157 1 return L2_ERROR; d159 3 a161 1 return L2_ERROR; d165 3 a167 1 return L2_ERROR; d169 1 d173 1 d178 1 d180 3 a182 1 return L2_ERROR; d184 3 a186 1 return L2_ERROR; d192 3 a194 1 return L2_ERROR; d198 1 d202 1 d207 1 d209 3 a211 1 return L2_ERROR; d215 2 d219 2 d225 2 d228 1 d232 1 d235 1 d237 3 a239 1 return L2_ERROR; d242 3 a244 1 return L2_ERROR; d246 3 a248 1 return L2_ERROR; d250 1 d254 1 d257 1 d260 1 d264 1 d267 1 d270 1 @ 1.14 log @Bugfix channel API: L2_OK -> NULL, states have to be remembered for all channels in a stack, not just the last found one. @ text @a72 1 l2_channel_t *chFirst; d78 2 a79 2 chFirst = ch; while (ch != NULL && ch->handler.open == NULL) d81 1 d85 2 a86 7 if (rv == L2_OK) { ch = chFirst; do { ch->state = L2_CHSTATE_OPENED; ch = ch->downstream; } while (ch != NULL); } a128 1 l2_channel_t *chFirst; a131 1 chFirst = ch; d134 2 a135 1 while (ch != NULL && ch->handler.close == NULL) d137 1 d141 2 a142 7 if (rv == L2_OK) { ch = chFirst; do { ch->state = L2_CHSTATE_CREATED; ch = ch->downstream; } while (ch != NULL); } @ 1.13 log @Bring into conformance with new l2_result_t channel handler return standard. @ text @d73 1 d79 2 a80 1 while (ch != NULL && ch->handler.open == L2_OK) d85 7 a91 2 if (rv == L2_OK) ch->state = L2_CHSTATE_OPENED; d107 1 a107 1 while (ch != NULL && ch->handler.write == L2_OK) d123 1 a123 1 while (ch != NULL && ch->handler.flush == L2_OK) d134 1 d138 1 d141 1 a141 1 while (ch != NULL && ch->handler.close == L2_OK) d146 7 a152 2 if (rv == L2_OK) ch->state = L2_CHSTATE_CREATED; d165 1 a165 1 while (ch != NULL && ch->handler.destroy == L2_OK) @ 1.12 log @Revamp channel handler API: Instead of passing the downstream channel to all channels we instead provide a l2_channel_downstream() function and provide the current channel. This way the handler API is prototype-wise fully orthogonal with the channel API (which it implements) and we no longer pass information to 2/3 of our (output) channels which is of no use there. Additionally add a channel type field to l2_handler_t which allows a handler to say what type of channel it implements (filter or output). This information is now used in l2_channel_stack() to make sure that one can only stack a filter channel on top of another channel. For convinience reasons there is also a new l2_channel_type() function which allows one to query the type of a particular channel. @ text @d78 1 a78 1 while (ch != NULL && ch->handler.open == NULL) d100 1 a100 1 while (ch != NULL && ch->handler.write == NULL) d116 1 a116 1 while (ch != NULL && ch->handler.flush == NULL) d132 1 a132 1 while (ch != NULL && ch->handler.close == NULL) d151 1 a151 1 while (ch != NULL && ch->handler.destroy == NULL) @ 1.11 log @Wohhooooo! Here comes the underlying message formatting support: 1. renamed l2_channel_setparam() to l2_util_setparam() because it is just a utility function and is not tied to any channel. 2. moved l2_util_setparam() to its own l2_ut_param.c source file. 3. added l2_ut_format.c which contains a slightly adjusted version of Str's str_format() stuff under the name l2_util_format(). 4. use l2_util_format() in l2_stream.c instead of vsnprintf() and this way finally support l2_formatter_t callbacks. 5. cleanup adjustments to the l2_stream_formatter() API. Let's rock... @ text @d48 1 a48 1 if (ch->handler.create(&ch->context) == L2_ERROR) { a54 11 l2_result_t l2_channel_stack(l2_channel_t *ch, l2_channel_t *chTop) { if (ch == NULL || chTop == NULL) return L2_ERROR; if ( ch->state != L2_CHSTATE_CREATED || chTop->state != L2_CHSTATE_CREATED) return L2_ERROR; chTop->downstream = ch; return L2_OK; } d65 1 a65 1 rv = ch->handler.configure(&ch->context, fmt, ap); d82 1 a82 1 rv = ch->handler.open(&ch->context, ch->downstream); d104 1 a104 1 rv = ch->handler.write(&ch->context, ch->downstream, buf, bufsize); d120 1 a120 1 rv = ch->handler.flush(&ch->context, ch->downstream); d136 1 a136 1 rv = ch->handler.close(&ch->context, ch->downstream); d154 1 a154 1 rv = ch->handler.destroy(&ch->context); d159 27 @ 1.10 log @- replace "int" with "l2_result_t" in L2 channel API - use a 2^n for L2_LEVEL_XXX in order to be able to create mask - remember loglevel for each channel - rewrite test suite @ text @a171 70 l2_result_t l2_channel_setparams(l2_param_t pa[], const char *fmt, va_list ap) { const char *cpB, *cpE; const char *cpC, *cpG; int ok; int i; if (pa == NULL || fmt == NULL || ap == NULL) return L2_ERROR; cpE = fmt; while (*cpE != '\0') { /* determine begin of parameter name */ cpB = cpE; while (*cpB == ',') cpB++; /* determine end of parameter name */ cpE = cpB; while (*cpE != ',' && *cpE != '\0') cpE++; /* try to match with configured parameters */ ok = FALSE; for (i = 0; pa[i].name != NULL; i++) { cpC = pa[i].name; cpG = cpB; while (*cpC != '\0' && cpG < cpE) { if (*cpC != *cpG) break; cpC++; cpG++; } if (*cpC == '\0' && cpG == cpE) { /* parameter matched, so store value */ switch (pa[i].type) { case L2_TYPE_CHAR: *(char *)(pa[i].store) = va_get(ap, char); break; case L2_TYPE_SHORT: *(short *)(pa[i].store) = va_get(ap, short); break; case L2_TYPE_INT: *(int *)(pa[i].store) = va_get(ap, int); break; case L2_TYPE_LONG: *(long *)(pa[i].store) = va_get(ap, long); break; case L2_TYPE_FLOAT: *(float *)(pa[i].store) = va_get(ap, float); break; case L2_TYPE_DOUBLE: *(double *)(pa[i].store) = va_get(ap, double); break; case L2_TYPE_CHARPTR: *(char **)(pa[i].store) = va_get(ap, charptr); break; case L2_TYPE_VOIDPTR: *(void **)(pa[i].store) = va_get(ap, voidptr); break; } ok = TRUE; break; } } if (!ok) return L2_ERROR; } return L2_OK; } @ 1.9 log @remove useless assign @ text @d55 1 a55 1 l2_channel_t *l2_channel_stack(l2_channel_t *ch1, l2_channel_t *ch2) d57 7 a63 7 if (ch1 == NULL || ch2 == NULL) return NULL; if ( ch1->state != L2_CHSTATE_CREATED || ch2->state != L2_CHSTATE_CREATED) return NULL; ch1->downstream = ch2; return ch1; d66 1 a66 1 int l2_channel_configure(l2_channel_t *ch, const char *fmt, ...) d68 1 a68 1 int rv; d81 1 a81 1 int l2_channel_open(l2_channel_t *ch) d99 1 a99 1 int l2_channel_write(l2_channel_t *ch, const char *buf, size_t bufsize) d119 1 a119 1 int l2_channel_flush(l2_channel_t *ch) d135 1 a135 1 int l2_channel_close(l2_channel_t *ch) d153 1 a153 1 int l2_channel_destroy(l2_channel_t *ch) d172 1 a172 1 int l2_channel_setparams(l2_param_t pa[], const char *fmt, va_list ap) @ 1.8 log @When destroying a channel, erase its pointer to avoid a double-destruction mistake (possibly by both the channel and stream parent.) @ text @a168 1 ch = NULL; @ 1.7 log @Revert to last version due to new design of l2_ch_socket_t. @ text @d169 1 @ 1.6 log @Added unsigned short type to PARAMETER macros for sockaddr_in struct. @ text @a212 3 case L2_TYPE_USHORT: *(unsigned short *)(pa[i].store) = va_get(ap, ushort); break; @ 1.5 log @Fix more ossp.com references by replacing with the correct domain name ossp.org. @ text @d213 3 @ 1.4 log @o rename l2_error_t to l2_result_t o introduce internal channel state for API robustness @ text @d7 1 a7 1 ** can be found at http://www.ossp.com/pkg/l2/. @ 1.3 log @Implement a first cut for a buffer channel (still untested). @ text @d34 2 d44 4 a47 9 ch->below = NULL; ch->context.vp = NULL; ch->handler.create = h->create; ch->handler.configure = h->configure; ch->handler.open = h->open; ch->handler.write = h->write; ch->handler.flush = h->flush; ch->handler.close = h->close; ch->handler.destroy = h->destroy; d59 5 a63 2 ch2->below = ch1; return ch2; d73 2 d83 2 d87 2 d90 1 a90 1 ch = ch->below; d93 4 a96 1 return ch->handler.open(&ch->context, ch->below); d101 3 a103 1 if (ch == NULL || buf == NULL || bufsize <= 0) d105 1 a105 3 while (ch != NULL && ch->handler.write == NULL) ch = ch->below; if (ch == NULL) d111 6 a116 1 return ch->handler.write(&ch->context, ch->below, buf, bufsize); d121 2 d125 2 d128 1 a128 1 ch = ch->below; d131 2 a132 1 return ch->handler.flush(&ch->context, ch->below); d137 2 d141 2 d144 1 a144 1 ch = ch->below; d147 4 a150 1 return ch->handler.close(&ch->context, ch->below); d155 1 a155 1 int rv = L2_OK; d159 3 d163 1 a163 1 ch = ch->below; d166 2 d179 2 @ 1.2 log @Simplify things by merging the parameter stuff into l2_channel.c and the l2_stream_t related things into l2_stream.c @ text @d98 4 @ 1.1 log @Initial revision @ text @d137 68 @ 1.1.1.1 log @L2 initial source tree @ text @@