OSSP CVS Repository

ossp - ossp-pkg/fsl/fsl.c 1.44
Not logged in
[Honeypot]  [Browse]  [Directory]  [Home]  [Login
[Reports]  [Search]  [Ticket]  [Timeline
  [Raw

ossp-pkg/fsl/fsl.c 1.44
/*
**  OSSP fsl - Faking/Flexible Syslog Library
**  Copyright (c) 2002 Ralf S. Engelschall <rse@engelschall.com>
**  Copyright (c) 2002 The OSSP Project <http://www.ossp.org/>
**  Copyright (c) 2002 Cable & Wireless Deutschland <http://www.cw.com/de/>
**
**  This file is part of OSSP fsl, a syslog(3) API faking library which
**  can be found at http://www.ossp.org/pkg/lib/fsl/.
**
**  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.
**
**  fsl.c: OSSP fsl library
*/

/* standard includes we use */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>

/* standard include we re-implement */
#include <syslog.h>

/* third party (linked in) */
#include "l2.h"
#include "cfg.h"
#include "pcre.h"

/* autoconfiguration */
#include "config.h"

/* version */
#include "fsl_version.c"

/* pcre static vector size */
#define OVECSIZE 30

/* backward compatibility */
#ifndef LOG_PRIMASK
#define LOG_PRIMASK (LOG_EMERG|LOG_ALERT|LOG_CRIT|LOG_ERR|\
                     LOG_WARNING|LOG_NOTICE|LOG_INFO|LOG_DEBUG)
#endif
#ifndef LOG_PRI
#define LOG_PRI(p) ((p) & LOG_PRIMASK)
#endif

/* cleanup sequence macros */
#define STMT(stuff) do { stuff } while (0)
#define CU(returncode) STMT( rc = returncode; goto CUS; )
#define VCU STMT( goto CUS; )

/* prefix of configuration files */
#define FSL_PREFIX "fsl."

/* general return codes */
typedef enum {
    FSL_OK = 0,               /* everything ok */
    FSL_NOIDENT,              /* ok but no "ident" directive found or none matched */
    FSL_ERR_ARG,              /* invalid argument */
    FSL_ERR_USE,              /* invalid use */
    FSL_ERR_MEM,              /* no more memory available */
    FSL_ERR_SYS,              /* operating system error, see errno */
    FSL_ERR_CUS,              /* error handled and logged, just run clean up sequence and goodbye */
    FSL_ERR_INT               /* internal error */
} fsl_rc_t;

/* file buffer handling sub-API */
typedef struct {
    char   *base;
    size_t  used;
    size_t  size;
} buf_t;

/* mapping table for syslog(3) facilities to strings */
static struct {
    int facility;
    char *string;
} syslogfacility2string[] = {
    { LOG_AUTH,     "auth"     },
#ifdef LOG_AUTHPRIV
    { LOG_AUTHPRIV, "authpriv" },
#endif
#ifdef LOG_CONSOLE
    { LOG_CONSOLE,  "console"  },
#endif
    { LOG_CRON,     "cron"     },
    { LOG_DAEMON,   "daemon"   },
#ifdef LOG_FTP
    { LOG_FTP,      "ftp"      },
#endif
    { LOG_KERN,     "kern"     },
    { LOG_LOCAL0,   "local0"   },
    { LOG_LOCAL1,   "local1"   },
    { LOG_LOCAL2,   "local2"   },
    { LOG_LOCAL3,   "local3"   },
    { LOG_LOCAL4,   "local4"   },
    { LOG_LOCAL5,   "local5"   },
    { LOG_LOCAL6,   "local6"   },
    { LOG_LOCAL7,   "local7"   },
    { LOG_LPR,      "lpr"      },
    { LOG_MAIL,     "mail"     },
    { LOG_NEWS,     "news"     },
#ifdef LOG_NTP
    { LOG_NTP,      "ntp"      },
#endif
#ifdef LOG_SECURITY
    { LOG_SECURITY, "security" },
#endif
    { LOG_SYSLOG,   "syslog"   },
    { LOG_USER,     "user"     },
    { LOG_UUCP,     "uucp"     },
    { -1,            NULL      }
};

/* mapping table for syslog(3) levels/priorities strings/numbers to L2 levels */
static struct {
    char       *string;
    int         level;
    l2_level_t  deflevelmap;
} sysloglevel2string[] = {
    { "emerg",   LOG_EMERG,   L2_LEVEL_PANIC    },
    { "alert",   LOG_ALERT,   L2_LEVEL_PANIC    },
    { "crit",    LOG_CRIT,    L2_LEVEL_CRITICAL },
    { "err",     LOG_ERR,     L2_LEVEL_ERROR    },
    { "warning", LOG_WARNING, L2_LEVEL_WARNING  },
    { "notice",  LOG_NOTICE,  L2_LEVEL_NOTICE   },
    { "info",    LOG_INFO,    L2_LEVEL_INFO     },
    { "debug",   LOG_DEBUG,   L2_LEVEL_DEBUG    },
    { NULL,      -1,          -1                }
};

/* mapping table for L2 level strings to L2 level numbers */
static struct {
    char *string;
    l2_level_t level;
} l2level2string[] = {
    { "none",     0,                  },
    { "panic",    L2_LEVEL_PANIC,     },
    { "critical", L2_LEVEL_CRITICAL,  },
    { "error",    L2_LEVEL_ERROR,     },
    { "warning",  L2_LEVEL_WARNING,   },
    { "notice",   L2_LEVEL_NOTICE,    },
    { "info",     L2_LEVEL_INFO,      },
    { "trace",    L2_LEVEL_TRACE,     },
    { "debug",    L2_LEVEL_DEBUG,     },
    { NULL,       -1                  }
};

/* type of run-time syslog(3) to L2 level mapping table */
typedef struct {
    int        syslog;
    l2_level_t l2;
} levelmap_t;

/* internal global context structure */
static struct {
    l2_env_t     *l2_fslenv;
    l2_channel_t *l2_fslnch;
    l2_env_t     *l2_env;
    l2_channel_t *l2_nch;
    levelmap_t   *levelmap;
    int           maskpri;
    int           logopt;
    int           delayopen;
    int           triedopenlog;
    int           triedopenfsldebug;
    int           openfsldebugpermanenterror;
} ctx = { 
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    LOG_UPTO(LOG_DEBUG),
    0,
    FALSE,
    FALSE,
    FALSE,
    FALSE
};

/* internal debugging function */
static void fsldebug(l2_level_t level, const char *message, ...)
{
    va_list ap;
    char cp[8];

    va_start(ap, message);
    /* note that a L2_ERR_USE is only returned by l2_channel_vlog() if the channel is not yet open */
    if (ctx.l2_fslnch == NULL || (l2_channel_vlog(ctx.l2_fslnch, level, message, ap) == L2_ERR_USE))
        if (level & L2_LEVEL_UPTO(L2_LEVEL_ERROR)) {
            fprintf( stderr, "%s: ", l2_util_l2s(cp, sizeof(cp), '\0', level) == L2_OK ? cp[0]&=0xDF, cp : "Log");
            vfprintf(stderr, message, ap);
            fprintf( stderr, "\n");
    }
    va_end(ap);
    return;
}

/* append contents of a file to buffer */
static fsl_rc_t appendfiletobuffer(buf_t *buffer, const char *filename)
{
    fsl_rc_t rc;
    int fd = -1;
    int filesize;
    int fileread;
    int bufferneed;

    if (filename == NULL || buffer == NULL)
        CU(FSL_ERR_ARG);

    fsldebug(L2_LEVEL_TRACE, "appendfiletobuffer() filename=\"%s\")", filename);

    if ((fd = open(filename, O_RDONLY)) == -1)
        CU(FSL_ERR_SYS);
    if ((filesize = (int)lseek(fd, 0, SEEK_END)) == -1)
        CU(FSL_ERR_SYS);

    bufferneed = buffer->used + filesize;
    if (bufferneed > buffer->size) {
        if (buffer->base == NULL) {
            if ((buffer->base = (char *)malloc(bufferneed)) == NULL)
                CU(FSL_ERR_MEM);
            buffer->size = bufferneed;
            buffer->used = 0;
        }
        else {
            if ((buffer->base = (char *)realloc(buffer->base, bufferneed)) == NULL)
                CU(FSL_ERR_MEM);
            buffer->size = bufferneed;
        }
    }

    if (lseek(fd, 0, SEEK_SET) == -1)
        CU(FSL_ERR_SYS);
    if ((fileread = (int)read(fd, buffer->base + buffer->used, (size_t)filesize)) == -1)
        CU(FSL_ERR_SYS);
    if (fileread != filesize)
        CU(FSL_ERR_USE);

    buffer->used += filesize;
    CU(FSL_OK);
CUS:
    if (fd != -1)
        close(fd);
    return rc;
}

/* read all possible files "fsl.*" into buffer */
static fsl_rc_t readallfiles(buf_t *buffer)
{
    fsl_rc_t rc;
    DIR *dp = NULL;
    struct dirent *de;
    char *filename = NULL;
    int n;

    if (buffer == NULL)
        CU(FSL_ERR_ARG);
    fsldebug(L2_LEVEL_TRACE, "readallfiles() globbing \"%s/%s*\"", FSL_CFGDIR, FSL_PREFIX);

    if ((dp = opendir(FSL_CFGDIR)) == NULL)
        CU(FSL_ERR_SYS);

    rc = FSL_ERR_ARG;
    while ((de = readdir(dp)) != NULL) {
        n = strlen(de->d_name);
        if (   (n >= strlen(FSL_PREFIX))
            && (strncmp(de->d_name, FSL_PREFIX, strlen(FSL_PREFIX)) == 0)) {
            if ((filename = (char *)malloc(strlen(FSL_CFGDIR) + 1 + n + 1)) == NULL)
                CU(FSL_ERR_MEM);
            filename[0] = '\0';
            strcat(filename, FSL_CFGDIR);
            strcat(filename, "/");
            strcat(filename, de->d_name);
            if (appendfiletobuffer(buffer, filename) == FSL_OK)
                rc = FSL_OK;
            free(filename);
            filename = NULL;
        }
    }
    CU(rc);
CUS:
    if (dp != NULL)
        closedir(dp);
    if (filename != NULL)
        free(filename);
    return rc;
}

/* OSSP cfg node tree traversal function (recursive) */
static void traverse(cfg_t *cfg, cfg_node_t *cfgnode)
{
    int rc;
    cfg_node_t *cfgchld;
    cfg_rc_t cfgrv;
    cfg_node_type_t cfgtyp;
    char *cp;
    int cfgchilds;

    while (cfgnode != NULL) {
        if ((cfgrv = cfg_node_get(cfg, cfgnode, CFG_NODE_ATTR_TOKEN, &cp)) != CFG_OK) {
            (void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "traverse: cfg_node_get(CFG_NODE_ATTR_TOKEN) failed: %s (%d)", cp, cfgrv); CU(1); }
        if ((cfgrv = cfg_node_get(cfg, cfgnode, CFG_NODE_ATTR_TYPE, &cfgtyp)) != CFG_OK) {
            (void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "traverse: cfg_node_get(CFG_NODE_ATTR_TYPE) failed: %s (%d)", cp, cfgrv); CU(1); }
        if ((cfgrv = cfg_node_get(cfg, cfgnode, CFG_NODE_ATTR_CHILDS, &cfgchilds)) != CFG_OK) {
            (void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "traverse: cfg_node_get(CFG_NODE_ATTR_CHILDS) failed: %s (%d)", cp, cfgrv); CU(1); }
        fsldebug(L2_LEVEL_DEBUG, "traverse: cfgnode=0x%.8lx[%d], cp=\"%s\", type=%d", (unsigned long)cfgnode, cfgchilds, (cp != NULL ? cp : "<NULL>"), cfgtyp);
        if ((cfgrv = cfg_node_get(cfg, cfgnode, CFG_NODE_ATTR_CHILD1, &cfgchld)) != CFG_OK) {
            (void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "traverse: cfg_node_get(CFG_NODE_ATTR_CHILD1) failed: %s (%d)", cp, cfgrv); CU(1); }
        if (cfgchld != NULL)
            traverse(cfg, cfgchld);
        if ((cfgrv = cfg_node_get(cfg, cfgnode, CFG_NODE_ATTR_RBROTH, &cfgnode)) != CFG_OK) {
            (void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "traverse: cfg_node_get(CFG_NODE_ATTR_RBROTH) failed: %s (%d)", cp, cfgrv); CU(1); }
    }
    CU(0);
CUS:
    return;
}

/* Substitute $[0-9] in inbuf[inlen] with captured strings passed through capary[nary].
 * Returns the number of characters (to be) written to output.  Output can be suppressed
 * by passing a NULL outpbuf. The terminating NUL is not counted but will be written!
 */
static int substcapture(const char *inbuf, int inlen, const char **capary, int nary, char *outbuf)
{
    int  i; /* input cursor, current position */
    int  n; /* outbuf cursor, next possible write position */
    int  m; /* index to capary */
    char c; /* scratch variable */

    i = 0;
    n = 0;
    while (i < inlen - 1 /* last char cannot start a two-character sequence, skipping it allows safe look ahead */) {
        c = inbuf[i];
        if (c == '$') {
            c = inbuf[i+1];
            if (c == '$') {
                if (outbuf != NULL)
                    outbuf[n] = '$';
                i += 2;
                n++;
            }
            else if ((c >= '0') && (c <= '9')) {
                m = c - '0';
                if (outbuf != NULL) {
                    outbuf[n] = '\0';
                    strcat(&outbuf[n], ((m < nary) && (capary[m] != NULL)) ? capary[m] : "");
                }
                i += 2;
                n += ((m < nary) && (capary[m] != NULL)) ? strlen(capary[m]) : 0;
            }
            else {
                if (outbuf != NULL)
                    outbuf[n] = '$';
                i++;
                n++;
                if (outbuf != NULL)
                    outbuf[n] = c;
                i++;
                n++;
            }
        }
        else {
            if (outbuf != NULL)
                outbuf[n] = c;
            i++;
            n++;
        }
    }
    if (outbuf != NULL)
        outbuf[n] = '\0';
    return n;
}

/*  process OSSP cfg node tree directives 
    mode=0 processes map/ident,
    mode=1 processes default
 */
static fsl_rc_t processcfg(cfg_t *cfg, char *cpISF, int mode)
{
    fsl_rc_t rc;
    cfg_rc_t          cfgrv;
    cfg_node_t       *cfgseq;
    cfg_node_t       *cfgdir;
    cfg_node_t       *cfgarg;
    cfg_node_type_t   cfgtype;
    int               cfgnumc;
    int               cfgnume;
    char             *cfgargtoka[3];
    char             *cfgargtok;
    char             *argident;
    char             *argmatch;
    char             *argl2spec;
    char             *cp; /* scratch variable */
    int               i;  /* scratch variable */
    int               n;  /* scratch variable */
    int               ovec[OVECSIZE];
    const char      **acpMatch;
    l2_channel_t     *ch; /* scratch variable */
    l2_result_t       l2rv;
    int               matchcount = 0;

    if ((cfg == NULL) || (cpISF == NULL) || (strlen(cpISF) < 3) || (mode < 0) || (mode > 1))
        return FSL_ERR_ARG;

    fsldebug(L2_LEVEL_TRACE, "processcfg() ident/facility=\"%s\", mode=%s", cpISF, mode == 0 ? "map/ident" : "default");

    /*  find configuration root node and check if it is a sequence and has one or more directives below it */
    if ((cfgrv = cfg_node_root(cfg, &cfgseq)) != CFG_OK) {
        (void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_root() failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }

    /*  only dump once */
    if (mode == 0)
        traverse(cfg, cfgseq);

    if ((cfgrv = cfg_node_get(cfg, cfgseq, CFG_NODE_ATTR_TYPE, &cfgtype)) != CFG_OK) {
        (void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_TYPE) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
    if (cfgtype != CFG_NODE_TYPE_SEQ) {
        fsldebug(L2_LEVEL_ERROR, "processcfg: expected sequence"); CU(FSL_ERR_CUS); }
    if ((cfgrv = cfg_node_get(cfg, cfgseq, CFG_NODE_ATTR_CHILDS, &cfgnumc)) != CFG_OK) {
        (void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_CHILDS) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
    if (cfgnumc < 1) {
        fsldebug(L2_LEVEL_ERROR, "processcfg: sequence is missing directives, expected 1, got %d", cfgnumc); CU(FSL_ERR_CUS); }

    /*  get first directive below sequence */
    if ((cfgrv = cfg_node_get(cfg, cfgseq, CFG_NODE_ATTR_CHILD1, &cfgdir)) != CFG_OK) {
        (void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_CHILD1) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
    while (cfgdir != NULL) {
        /*  check if operating on a directive */
        if ((cfgrv = cfg_node_get(cfg, cfgdir, CFG_NODE_ATTR_TYPE, &cfgtype)) != CFG_OK) {
            (void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_TYPE) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
        if (cfgtype != CFG_NODE_TYPE_DIR) {
            fsldebug(L2_LEVEL_ERROR, "processcfg: expected directive"); CU(FSL_ERR_CUS); }
        if ((cfgrv = cfg_node_get(cfg, cfgdir, CFG_NODE_ATTR_CHILDS, &cfgnumc)) != CFG_OK) {
            (void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_CHILDS) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }

        /*  process first child of directive, check if it is an argument and has a valid token attached */
        if ((cfgrv = cfg_node_get(cfg, cfgdir, CFG_NODE_ATTR_CHILD1, &cfgarg)) != CFG_OK) {
            (void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_CHILD1) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
        if (cfgarg == NULL) {
            fsldebug(L2_LEVEL_ERROR, "processcfg: first argument is NULL"); CU(FSL_ERR_CUS); }
        if ((cfgrv = cfg_node_get(cfg, cfgarg, CFG_NODE_ATTR_TYPE, &cfgtype)) != CFG_OK) {
            (void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_TYPE) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
        if (cfgtype != CFG_NODE_TYPE_ARG) {
            fsldebug(L2_LEVEL_ERROR, "processcfg: expected first argument, got %d", cfgtype); CU(FSL_ERR_CUS); }
        if ((cfgrv = cfg_node_get(cfg, cfgarg, CFG_NODE_ATTR_TOKEN, &cfgargtok)) != CFG_OK) {
            (void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_TOKEN) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
        if (cfgargtok == NULL) {
            fsldebug(L2_LEVEL_ERROR, "processcfg: first argument has NULL data"); CU(FSL_ERR_CUS); }
        cfgargtoka[0] = cfgargtok;

        cfgnume = 0;
        if (strcmp(cfgargtoka[0], "ident") == 0)
            cfgnume = 3;
        if (strcmp(cfgargtoka[0], "default") == 0)
            cfgnume = 3;
        if (strcmp(cfgargtoka[0], "map") == 0)
            cfgnume = 3;
        if (cfgnume == 0) {
            fsldebug(L2_LEVEL_ERROR, "processcfg: syntax error, invalid argument \"%s\"", cfgargtoka[0]); CU(FSL_ERR_CUS); }
        if (cfgnume != cfgnumc) {
            fsldebug(L2_LEVEL_ERROR, "processcfg: directive missing arguments, expected %d, got %d", cfgnume, cfgnumc); CU(FSL_ERR_CUS); }

        for (i = 1; i < cfgnume; i++) {
            /*  process right brother of argument, check if it is an argument and has a valid token attached */
            if ((cfgrv = cfg_node_get(cfg, cfgarg, CFG_NODE_ATTR_RBROTH, &cfgarg)) != CFG_OK) {
                (void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_RBROTH) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
            if (cfgarg == NULL) {
                fsldebug(L2_LEVEL_ERROR, "processcfg: argument %d is NULL", i); CU(FSL_ERR_CUS); }
            if ((cfgrv = cfg_node_get(cfg, cfgarg, CFG_NODE_ATTR_TYPE, &cfgtype)) != CFG_OK) {
                (void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_TYPE) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
            if (cfgtype != CFG_NODE_TYPE_ARG) {
                fsldebug(L2_LEVEL_ERROR, "processcfg: expected argument %d", i); CU(FSL_ERR_CUS); }
            if ((cfgrv = cfg_node_get(cfg, cfgarg, CFG_NODE_ATTR_TOKEN, &cfgargtok)) != CFG_OK) {
                (void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_TOKEN) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
            if (cfgargtok == NULL) {
                fsldebug(L2_LEVEL_ERROR, "processcfg: argument %d has NULL data", i); CU(FSL_ERR_CUS); }
            cfgargtoka[i] = cfgargtok;
        }

        if ((strcmp(cfgargtoka[0], "ident") == 0) || (strcmp(cfgargtoka[0], "default") == 0)) {
            argident  = cfgargtoka[0];
            argmatch  = cfgargtoka[1];
            argl2spec = cfgargtoka[2];

            if (   ((mode == 0) && (strcmp(cfgargtoka[0], "ident") == 0))
                || ((mode == 1) && (strcmp(cfgargtoka[0], "default") == 0))
                  ) {
                /*  process the directive using the three arguments found */
                fsldebug(L2_LEVEL_DEBUG, "processcfg: argident=%s, argmatch=%s, argl2spec=%s", argident, argmatch, argl2spec);
                {
                    pcre       *pcreRegex;
                    pcre_extra *pcreExtra;
                    const char *cpError;
                    int         iError;
                    int         nMatch;

                    /* compile regular expression into finite state machine and optimize */
                    if ((pcreRegex = pcre_compile(argmatch, PCRE_CASELESS, &cpError, &iError, NULL)) == NULL) {
                        fsldebug(L2_LEVEL_ERROR, "processcfg: pcre_compile() failed with error %s (%d)", cpError, iError); CU(FSL_ERR_CUS); }
                    pcreExtra = pcre_study(pcreRegex, 0, &cpError);
                    if (cpError != NULL) {
                        fsldebug(L2_LEVEL_ERROR, "processcfg: pcre_study() failed with error %s", cpError); CU(FSL_ERR_CUS); }

                    nMatch = pcre_exec(pcreRegex, pcreExtra, cpISF, strlen(cpISF), 0, 0, ovec, OVECSIZE);
                    if (nMatch < 0)
                        fsldebug(L2_LEVEL_TRACE, "processcfg: matching ident/facility \"%s\" against section \"%s\" failed.", cpISF, argmatch);
                    else
                        if (nMatch == 0)
                            fsldebug(L2_LEVEL_TRACE, "processcfg: matching ident/facility \"%s\" against section \"%s\" succeeded, found $0", cpISF, argmatch);
                        else
                            fsldebug(L2_LEVEL_TRACE, "processcfg: matching ident/facility \"%s\" against section \"%s\" succeeded, found $0...$%d", cpISF, argmatch, (nMatch-1) > 9 ? 9 : (nMatch-1));
                    if (nMatch >= 1) {
                        pcre_get_substring_list(cpISF, ovec, nMatch, &acpMatch);
                        if (acpMatch != NULL)
                            for (i = 0; i < nMatch; i++)
                                fsldebug(L2_LEVEL_DEBUG, "processcfg: regex reference[%d]=\'%s\'", i, acpMatch[i] == NULL ? "(UNDEFINED)" : acpMatch[i]);
                        n = substcapture(argl2spec, strlen(argl2spec), acpMatch, nMatch, NULL);
                        if ((cp = (char *)malloc(n + 1)) == NULL) {
                            fsldebug(L2_LEVEL_ERROR, "processcfg: malloc() failed"); CU(FSL_ERR_CUS); }
                        if (substcapture(argl2spec, strlen(argl2spec), acpMatch, nMatch, cp) != n) {
                            fsldebug(L2_LEVEL_ERROR, "processcfg: substcapture() failed"); CU(FSL_ERR_CUS); }
                        argl2spec = cp;
                        fsldebug(L2_LEVEL_DEBUG, "processcfg: argident=%s, argmatch=%s, argl2spec=%s", argident, argmatch, argl2spec);

                        /* create L2 channel throuh spec and link into root channel */
                        if ((l2rv = l2_spec(&ch, ctx.l2_env, "%s", argl2spec)) != L2_OK) {
                            cp = l2_env_strerror(ctx.l2_env, l2rv); fsldebug(L2_LEVEL_ERROR, "processcfg: logging failed to create stream from spec %s(%d)", cp, l2rv); CU(FSL_ERR_CUS); }
                        if ((l2rv = l2_channel_link(ctx.l2_nch, L2_LINK_CHILD, ch, NULL)) != L2_OK) {
                            cp = l2_env_strerror(ctx.l2_env, l2rv); fsldebug(L2_LEVEL_ERROR, "processcfg: logging failed to link child channel %s(%d)", cp, l2rv); CU(FSL_ERR_CUS); }
                        matchcount++;
                        free(argl2spec);
                    }
                }
            }
            else
                fsldebug(L2_LEVEL_DEBUG, "processcfg: ignoring %s in mode %d", cfgargtoka[0], mode);
        }
        else if (strcmp(cfgargtoka[0], "map") == 0) {
            if (mode == 0) {
                int mapfrom;
                int mapto;

                for (i = 0, mapfrom = -1; (mapfrom == -1) && (sysloglevel2string[i].string != NULL); i++) {
                    if (strcmp(sysloglevel2string[i].string, cfgargtoka[1]) == 0)
                        mapfrom = i;
                }
                if (mapfrom == -1) {
                    fsldebug(L2_LEVEL_ERROR, "processcfg: trying to map from unknown syslog level \"%s\"", cfgargtoka[1]); CU(FSL_ERR_CUS); }

                for (i = 0, mapto = -1; (mapto == -1) && (l2level2string[i].string != NULL); i++) {
                    if (strcmp(l2level2string[i].string, cfgargtoka[2]) == 0)
                        mapto = i;
                }
                if (mapto == -1) {
                    fsldebug(L2_LEVEL_ERROR, "processcfg: trying to map to unknown l2 level \"%s\"", cfgargtoka[2]); CU(FSL_ERR_CUS); }
                
                ctx.levelmap[mapfrom].l2 = l2level2string[mapto].level;
                fsldebug(L2_LEVEL_DEBUG, "processcfg: map levelmap[%10s/%d].l2 = l2level2string[%10s/%d].level = 0x%.8lx", cfgargtoka[1], mapfrom, cfgargtoka[2], mapto, (unsigned long)l2level2string[mapto].level);
            }
            else
                fsldebug(L2_LEVEL_DEBUG, "processcfg: ignoring %s in mode %d", cfgargtoka[0], mode);
        }
        else {
            fsldebug(L2_LEVEL_ERROR, "processcfg: internal, argument \"%s\" not implemented", cfgargtoka[0]); CU(FSL_ERR_CUS); }

        /*  get right brother of current directive */
        if ((cfgrv = cfg_node_get(cfg, cfgdir, CFG_NODE_ATTR_RBROTH, &cfgdir)) != CFG_OK) {
            (void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_RBROTH) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
    }

    fsldebug(L2_LEVEL_TRACE, "processcfg: matched %d sections while looking for %s sections", matchcount, mode == 0 ? "ident" : "default");
    if (matchcount == 0)
        CU(FSL_NOIDENT);
    else
        CU(FSL_OK);
CUS:
    return rc;
}

static void closefsllog()
{
    if (ctx.l2_fslnch != NULL) {
        l2_channel_destroy(ctx.l2_fslnch);
        ctx.l2_fslnch = NULL;
    }
    if (ctx.l2_fslenv != NULL) {
        l2_env_destroy(ctx.l2_fslenv);
        ctx.l2_fslenv = NULL;
    }
    ctx.triedopenfsldebug = FALSE;
}

static void openfsldebug()
{
    int rc = FSL_OK;
    char *argl2spec;
    l2_result_t   l2rv;
    char *cp; /* scratch variable */
    l2_channel_t *ch; /* scratch variable */

    if (ctx.triedopenfsldebug || ctx.openfsldebugpermanenterror)
        return;

    /* properly prepare for repeated execution */
    ctx.triedopenfsldebug = TRUE;

    /* create OSSP l2 environment for fsl itself (internal logging) */
    if ((argl2spec = getenv("FSL_DEBUG")) == NULL)
        argl2spec = FSL_DEBUG;
    if (strlen(argl2spec) > 0) {
        if ((l2rv = l2_env_create(&ctx.l2_fslenv)) != L2_OK) {
            cp = l2_env_strerror(ctx.l2_fslenv, l2rv); fsldebug(L2_LEVEL_ERROR, "openlog: failed to create L2 environment (%d) for fsl", l2rv); CU(FSL_ERR_INT); }
        if ((l2rv = l2_env_levels(ctx.l2_fslenv, L2_LEVEL_ALL, L2_LEVEL_NONE)) != L2_OK) {
            cp = l2_env_strerror(ctx.l2_fslenv, l2rv); fsldebug(L2_LEVEL_ERROR, "openlog: logging failed to set global logging level defaults %s(%d) for fsl", cp, l2rv); CU(FSL_ERR_INT); }
        if ((l2rv = l2_env_formatter(ctx.l2_fslenv, 'D', l2_util_fmt_dump, NULL)) != L2_OK) {
            cp = l2_env_strerror(ctx.l2_fslenv, l2rv); fsldebug(L2_LEVEL_ERROR, "openlog: logging failed to register dump formatter %s(%d) for fsl", cp, l2rv); CU(FSL_ERR_INT); }
        if ((l2rv = l2_env_formatter(ctx.l2_fslenv, 'S', l2_util_fmt_string, NULL)) != L2_OK) {
            cp = l2_env_strerror(ctx.l2_fslenv, l2rv); fsldebug(L2_LEVEL_ERROR, "openlog: logging failed to register string formatter %s(%d) for fsl", cp, l2rv); CU(FSL_ERR_INT); }
        if ((l2rv = l2_env_formatter(ctx.l2_fslenv, 'm', l2_util_fmt_errno, NULL)) != L2_OK) {
            cp = l2_env_strerror(ctx.l2_fslenv, l2rv); fsldebug(L2_LEVEL_ERROR, "openlog: logging failed to register errno formatter %s(%d) for fsl", cp, l2rv); CU(FSL_ERR_INT); }
        if ((l2rv = l2_channel_create(&ctx.l2_fslnch, ctx.l2_fslenv, "noop")) != L2_OK) {
            cp = l2_env_strerror(ctx.l2_fslenv, l2rv); fsldebug(L2_LEVEL_ERROR, "openlog: logging failed to create noop channel; %s(%d) for fsl", cp, l2rv); CU(FSL_ERR_INT); }
        if ((l2rv = l2_spec(&ch, ctx.l2_fslenv, "%s", argl2spec)) != L2_OK) {
            cp = l2_env_strerror(ctx.l2_fslenv, l2rv); fsldebug(L2_LEVEL_ERROR, "openlog: logging failed to create stream from spec %s(%d) for fsl", cp, l2rv);
            if (l2rv == L2_ERR_ARG)
                CU(FSL_ERR_ARG);
            else
                CU(FSL_ERR_INT);
        }
        if ((l2rv = l2_channel_link(ctx.l2_fslnch, L2_LINK_CHILD, ch, NULL)) != L2_OK) {
            cp = l2_env_strerror(ctx.l2_fslenv, l2rv); fsldebug(L2_LEVEL_ERROR, "openlog: logging failed to link child channel %s(%d) for fsl", cp, l2rv); CU(FSL_ERR_INT); }
        if ((l2rv = l2_channel_open(ctx.l2_fslnch)) != L2_OK) {
            cp = l2_env_strerror(ctx.l2_fslenv, l2rv); fsldebug(L2_LEVEL_ERROR, "openlog: logging failed to open channel stream %s(%d) for fsl", cp, l2rv); CU(FSL_ERR_INT); }
    }
CUS:
    if (rc != FSL_OK)
        closefsllog();
    if (rc == FSL_ERR_ARG)
        ctx.openfsldebugpermanenterror = TRUE;
}

/* POSIX API function openlog(3) */
void openlog(const char *ident, int logopt, int facility)
{
    int rc;
    buf_t buf;
    fsl_rc_t rv;
    cfg_t           *cfg;
    cfg_rc_t         cfgrv;
    char *cp; /* scratch variable */
    int   i;  /* scratch variable */
    char *cpIdent;
    char *cpFacility;
    char *cpISF;
    l2_result_t   l2rv;

    /* initialization */
    cfg = NULL;
    buf.base = NULL;
    buf.used = 0;
    buf.size = 0;
    cpIdent = NULL;
    cpISF = NULL;

    /* properly prepare for repeated execution */
    closelog();
    ctx.triedopenlog = TRUE;

    /* create OSSP l2 environment for fsl itself (internal logging) */
    openfsldebug();

    /* tracing */
    fsldebug(L2_LEVEL_TRACE, "openlog() ident=\"%s\", logopt=0x%.8lx, facility=0x%.8lx; caught by FSL %s", ident, logopt, facility, fsl_version.v_gnu);

    /* remember logopt and handle unsupported values */
    ctx.logopt = logopt;
    if (logopt & LOG_CONS)
        fsldebug(L2_LEVEL_WARNING, "openlog: ignore unsupported LOG_CONS");
#ifdef LOG_PERROR
    if (logopt & LOG_PERROR)
        fsldebug(L2_LEVEL_WARNING, "openlog: ignore unsupported LOG_PERROR (use OSSP l2 channel \"fd(filedescriptor=2)\" to emulate)");
#endif
    if (logopt & LOG_PID)
        fsldebug(L2_LEVEL_WARNING, "openlog: ignore unsupported LOG_PID (use OSSP l2 formatter %%P in prefix channel to emulate)");

    /* create default sysloglevel to l2_level mapping */
    fsldebug(L2_LEVEL_DEBUG, "openlog: create default syslog(3) to OSSP l2 level/priority mapping table");
    for (i = 0; sysloglevel2string[i].string != NULL; i++)
        ;
    if ((ctx.levelmap = (levelmap_t *)malloc(i * sizeof(levelmap_t))) == NULL) {
        fsldebug(L2_LEVEL_ERROR, "openlog: malloc() failed"); CU(1); }
    for (i = 0; sysloglevel2string[i].string != NULL; i++) {
        ctx.levelmap[i].syslog = sysloglevel2string[i].level;
        ctx.levelmap[i].l2     = sysloglevel2string[i].deflevelmap;
        fsldebug(L2_LEVEL_DEBUG, "openlog: ctx.levelmap[%d].syslog = 0x%.8lx, ctx.levelmap[%d].l2 = 0x%.8lx", 
                 i, (unsigned long)ctx.levelmap[i].syslog, i, (unsigned long)ctx.levelmap[i].l2);
    }

    /* create OSSP l2 environment for main application */
    if ((l2rv = l2_env_create(&ctx.l2_env)) != L2_OK) {
        cp = l2_env_strerror(ctx.l2_env, l2rv); fsldebug(L2_LEVEL_ERROR, "openlog: failed to create OSSP l2 environment: (%d)", l2rv); CU(1); }
    if ((l2rv = l2_env_levels(ctx.l2_env, L2_LEVEL_ALL, L2_LEVEL_NONE)) != L2_OK) {
        cp = l2_env_strerror(ctx.l2_env, l2rv); fsldebug(L2_LEVEL_ERROR, "openlog: logging failed to set global logging level defaults: %s(%d)", cp, l2rv); CU(1); }
    if ((l2rv = l2_env_formatter(ctx.l2_env, 'D', l2_util_fmt_dump, NULL)) != L2_OK) {
        cp = l2_env_strerror(ctx.l2_env, l2rv); fsldebug(L2_LEVEL_ERROR, "openlog: logging failed to register dump formatter: %s(%d)", cp, l2rv); CU(1); }
    if ((l2rv = l2_env_formatter(ctx.l2_env, 'S', l2_util_fmt_string, NULL)) != L2_OK) {
        cp = l2_env_strerror(ctx.l2_env, l2rv); fsldebug(L2_LEVEL_ERROR, "openlog: logging failed to register string formatter: %s(%d)", cp, l2rv); CU(1); }
    if ((l2rv = l2_env_formatter(ctx.l2_env, 'm', l2_util_fmt_errno, NULL)) != L2_OK) {
        cp = l2_env_strerror(ctx.l2_env, l2rv); fsldebug(L2_LEVEL_ERROR, "openlog: logging failed to register errno formatter: %s(%d)", cp, l2rv); CU(1); }
    if ((l2rv = l2_channel_create(&ctx.l2_nch, ctx.l2_env, "noop")) != L2_OK) {
        cp = l2_env_strerror(ctx.l2_env, l2rv); fsldebug(L2_LEVEL_ERROR, "openlog: logging failed to create noop channel: %s(%d)", cp, l2rv); CU(1); }

    /* create IdentSlashFacility */
    if ((cpIdent = strdup((ident != NULL) ? ident : "unknown")) == NULL) {
        fsldebug(L2_LEVEL_ERROR, "openlog: strdup() failed"); CU(1); }
    cpFacility = "unknown";
    for (i = 0; syslogfacility2string[i].string != NULL; i++) {
        if (facility == syslogfacility2string[i].facility) {
            cpFacility = syslogfacility2string[i].string;
            break;
        }
    }
    if ((cpISF = (char *)malloc(strlen(cpIdent) + 1 + strlen(cpFacility) + 1)) == NULL) {
        fsldebug(L2_LEVEL_ERROR, "openlog: malloc() failed"); CU(1); }
    cpISF[0] = '\0';
    strcat(cpISF, cpIdent);
    strcat(cpISF, "/");
    strcat(cpISF, cpFacility);

    /* read configuration file(s) into buffer */
    if ((rv = readallfiles(&buf)) != FSL_OK) {
        fsldebug(L2_LEVEL_ERROR, "openlog: readallfiles() failed. Hint: last system error was \"%s\"(%d)", strerror(errno), errno); CU(1); }

    /* import configuration buffer into OSSP cfg node tree */
    if ((cfgrv = cfg_create(&cfg)) != CFG_OK) {
        (void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "openlog: cfg_create() failed with error %s (%d)", cp, cfgrv); CU(1); }
    if ((cfgrv = cfg_import(cfg, NULL, CFG_FMT_CFG, buf.base, buf.size)) != CFG_OK) {
        (void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "openlog: cfg_import() failed with error %s (%d)", cp, cfgrv); CU(1); }

    /*  process OSSP cfg node tree "map" and "ident" directives */
    if ((rv = processcfg(cfg, cpISF, 0)) != FSL_OK && (rv != FSL_NOIDENT)) {
        fsldebug(L2_LEVEL_ERROR, "openlog: processcfg() failed with an unrecoverable error (%d)", rv); CU(1); }

    /*  optionally process OSSP cfg node tree "default" directives */
    if ((rv == FSL_NOIDENT) && ((rv = processcfg(cfg, cpISF, 1)) != FSL_OK)) {
        fsldebug(L2_LEVEL_ERROR, "openlog: processcfg() failed with an unrecoverable error (%d)", rv); CU(1); }

    /*  open logging now or prepare for delayed open */
    if (logopt & LOG_NDELAY) {
        ctx.delayopen = TRUE;
        fsldebug(L2_LEVEL_TRACE, "openlog: logopt LOG_NDELAY delays open of L2 channel tree until first message is being logged");
    }
    else {
        if ((l2rv = l2_channel_open(ctx.l2_nch)) != L2_OK) {
            cp = l2_env_strerror(ctx.l2_env, l2rv); fsldebug(L2_LEVEL_ERROR, "openlog: logging failed to open channel stream %s(%d) immediately", cp, l2rv); CU(1); }
        fsldebug(L2_LEVEL_TRACE, "openlog: logging succeeded to open channel stream immediately");
    }

    CU(0);
CUS:
    if (cpISF != NULL)
        free(cpISF);
    if (cpIdent != NULL)
        free(cpIdent);
#if 0 /* FIXME */
    if (cfg != NULL)
        if ((cfgrv = cfg_destroy(cfg)) != CFG_OK) {
            (void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "openlog: cfg_destroy() failed with error %s (%d)", cp, cfgrv); CU(1); }
#endif
    if (buf.base != NULL)
        free(buf.base);
    if (rc != 0)
        closelog();
    return;
}

/* faked POSIX API function closelog(3) */
void closelog(void)
{
    fsldebug(L2_LEVEL_TRACE, "closelog()");
    closefsllog();
    if (ctx.l2_nch != NULL) {
        l2_channel_destroy(ctx.l2_nch);
        ctx.l2_nch = NULL;
    }
    if (ctx.l2_env != NULL) {
        l2_env_destroy(ctx.l2_env);
        ctx.l2_env = NULL;
    }
    if (ctx.levelmap != NULL) {
        free(ctx.levelmap);
        ctx.levelmap = NULL;
    }
    ctx.maskpri = LOG_UPTO(LOG_DEBUG);
    ctx.logopt = 0;
    ctx.delayopen = FALSE;
    ctx.triedopenlog = FALSE;
    return;
}

/* faked POSIX API function setlogmask(3) */
int setlogmask(int maskpri)
{
    int oldmask;

    fsldebug(L2_LEVEL_TRACE, "setlogmask() maskpri=0x%.8lx", maskpri);
    oldmask = ctx.maskpri;
    if (maskpri != 0)
        ctx.maskpri = maskpri;
    return oldmask;
}

/* faked POSIX API function syslog(3) */
void syslog(int priority, const char *message, ...)
{
    va_list args;

    /* wrap around vsyslog(3) */
    va_start(args, message);
    vsyslog(priority, message, args);
    va_end(args);
    return;
}

/* faked POSIX API function vsyslog(3) */
#ifdef HAVE_VSYSLOG_USVALIST
void vsyslog(int priority, const char *fmt, __va_list args)
#else
void vsyslog(int priority, const char *fmt, va_list args)
#endif
{
    unsigned int levelmask;
    int i;
    l2_result_t l2rv;
    char *cp;
    
    /* check for previous omitted attempt of initialization */
    if (ctx.l2_nch == NULL && !ctx.triedopenlog)
        openlog("fsl", 0, LOG_SYSLOG);

    /* check for previous proper initialization */
    if (ctx.l2_nch == NULL)
        return;

    /* check for delayed open */
    if ((ctx.logopt & LOG_NDELAY) && (ctx.delayopen == TRUE)) {
        if ((l2rv = l2_channel_open(ctx.l2_nch)) != L2_OK) {
            cp = l2_env_strerror(ctx.l2_env, l2rv); fsldebug(L2_LEVEL_ERROR, "vsyslog: logging failed to open channel stream %s(%d) delayed", cp, l2rv);
            closelog();
        }
        fsldebug(L2_LEVEL_TRACE, "vsyslog: logging succeeded to open channel stream delayed");
        ctx.delayopen = FALSE;
    }

    /* strip off facility */
    priority &= LOG_PRIMASK;
    fsldebug(L2_LEVEL_DEBUG, "vsyslog: priority=0x%.8lx, ctx.maskpri=0x%.8lx  ", (unsigned long)priority, (unsigned long)ctx.maskpri);

    /* check against maskpri */
    if ((LOG_MASK(priority) & ctx.maskpri) == 0) {
        fsldebug(L2_LEVEL_DEBUG, "vsyslog: short circuit maskpri");
        return;
    }

    levelmask = 0;
    for (i = 0; sysloglevel2string[i].string != NULL; i++) {
        fsldebug(L2_LEVEL_DEBUG, "vsyslog: ctx.levelmap[%d].syslog = 0x%.8lx, ctx.levelmap[%d].l2 = 0x%.8lx", i, (unsigned long)ctx.levelmap[i].syslog, i, (unsigned long)ctx.levelmap[i].l2);
        if (ctx.levelmap[i].syslog == priority) {
            levelmask = ctx.levelmap[i].l2;
            break;
        }
    }
    fsldebug(L2_LEVEL_DEBUG, "vsyslog: levelmask=0x%.8lx", (unsigned long)levelmask);

    /* the heart of FSL */
    if ((l2rv = l2_channel_vlog(ctx.l2_nch, levelmask, fmt, args)) != L2_OK) {
        cp = l2_env_strerror(ctx.l2_env, l2rv);
        fsldebug(L2_LEVEL_PANIC, "vsyslog: application logging failed: %s (%d)", cp, l2rv);
    }
    return;
}


CVSTrac 2.0.1