src/modules/logwatch_filter.c
author Tero Marttila <terom@fixme.fi>
Mon, 04 May 2009 20:55:04 +0300
branchnew-transport
changeset 168 a58ad50911fc
parent 138 a716c621cb90
permissions -rw-r--r--
refactor test.c into tests/*
#include "logwatch.h"
#include "../str.h"
#include "../log.h"

#include <stdlib.h>
#include <string.h>

#include <assert.h>

struct logwatch_filter* logwatch_filter_lookup (struct logwatch *ctx, const char *name)
{
    struct logwatch_filter *filter;
    
    // inspect each filter
    TAILQ_FOREACH(filter, &ctx->filters, logwatch_filters)
        if (strcmp(filter->name, name) == 0)
            return filter;
    
    // not found
    return NULL;
}

struct logwatch_filter* logwatch_filter (struct logwatch *ctx, const char *name, const struct logwatch_source *source,
        const char *pattern, const char *format, struct logwatch_chan *chan, struct error_info *err)
{
    struct logwatch_filter *filter;
    const char *err_msg;
    int err_offset;

    // alloc
    if ((filter = calloc(1, sizeof(*filter))) == NULL)
        JUMP_SET_ERROR(err, ERR_CALLOC);

    // optional source
    filter->source = source;

    // name?
    if (name && (filter->name = strdup(name)) == NULL)
        JUMP_SET_ERROR(err, ERR_STRDUP);
    
    // compile the regexp
    // XXX: any flags?
    if (pattern && (filter->regexp = pcre_compile(pattern, 0, &err_msg, &err_offset, NULL)) == NULL)
        JUMP_SET_ERROR_STR(err, ERR_PCRE_COMPILE, err_msg);

    // format?
    if (format && (filter->format = strdup(format)) == NULL)
        JUMP_SET_ERROR(err, ERR_STRDUP);
   
    // output channel
    filter->chan = chan;
 
    // add it
    TAILQ_INSERT_TAIL(&ctx->filters, filter, logwatch_filters);

    // mark it as registered
    filter->ctx = ctx;

    // ok
    return filter;

error:
    // cleanup
    if (filter)
        logwatch_filter_destroy(filter);
    
    return NULL;
}

void logwatch_filter_destroy (struct logwatch_filter *filter)
{
    if (filter->ctx)
        // unreigster
        TAILQ_REMOVE(&filter->ctx->filters, filter, logwatch_filters);
    
    // this if is probably not needed
    if (filter->regexp) 
        pcre_free(filter->regexp);
    
    if (filter->chan)    
        // release the logwatch_chan ref
        logwatch_chan_put(filter->chan);

    // free misc
    free(filter->format);
    free(filter->name);
    free(filter);
}

void logwatch_filter_remove (struct logwatch *ctx, const struct logwatch_source *source)
{
    struct logwatch_filter *filter, *next;
    
    // inspect each filter
    for (filter = TAILQ_FIRST(&ctx->filters); filter; filter = next) {
        next = TAILQ_NEXT(filter, logwatch_filters);
    
        // destroy matching
        if (filter->source == source)
            logwatch_filter_destroy(filter);
    }
}

/**
 * str_format context for logwatch_filter_apply operation
 */
struct logwatch_filter_str_format_ctx {
    /** The regexp we are processing */
    pcre *regexp;

    /** The subject string */
    const char *subject;

    /** The match group vector */
    int *groups;
    
    /** Number of match items in groups */
    size_t group_count;
};

/**
 * Our callback for str_format params used by logwatch_filter_apply()
 */
static err_t logwatch_filter_str_format_cb (const char *name, const char **value, ssize_t *value_len, void *arg)
{
    struct logwatch_filter_str_format_ctx *ctx = arg;
    int num;

    // look it up
    if (!ctx->regexp || (num = pcre_get_stringnumber(ctx->regexp, name)) < 0)
        // no such capture group
        return ERR_STR_FMT_NAME;

    // sanity check
    assert((size_t) num < ctx->group_count);

    // look up the start/end offsets
    int start = ctx->groups[num * 2 + 0];
    int end = ctx->groups[num * 2 + 1];

    // return a pointer to the subject
    *value = ctx->subject + start;
    *value_len = end - start;

    // ok
    return SUCCESS;
}

// number of pcre ovec elemnents required
#define LOGWATCH_FILTER_OVEC_ITEMS ((LOGWATCH_FILTER_GROUPS_MAX + 1) * 3)

// length of output line prefix
#define LOGWATCH_FILTER_OUT_PREFIX_LEN 16

int logwatch_filter_apply (const struct logwatch_filter *filter, const struct logwatch_source *source, const char *line, struct error_info *err)
{
    int ovec[LOGWATCH_FILTER_OVEC_ITEMS], ret;
    char buf[LOGWATCH_FILTER_OUT_MAX + 1], *buf_ptr = buf;
    size_t buf_size = sizeof(buf);

    // XXX: what to do for truncated data?

    // discard based on source?
    if (filter->source && source != filter->source)
        return 0;
    
    // match against the regexp?
    if (filter->regexp && (ret = pcre_exec(filter->regexp, NULL, line, strlen(line), 0, 0, ovec, LOGWATCH_FILTER_OVEC_ITEMS)) <= 0) {
        if (ret == PCRE_ERROR_NOMATCH)
            // no match, ignore
            return 0;

        else
            // misc. error
            // XXX: might be 0 for too many groups
            JUMP_SET_ERROR_EXTRA(err, ERR_PCRE_EXEC, ret);
    }

    // blackhole?
    if (!filter->chan)
        return 1;

    // format?
    if (filter->format) {
        // setup the ctx
        struct logwatch_filter_str_format_ctx ctx = {
            .regexp         = filter->regexp, 
            .subject        = line,
            .groups         = ovec,
            .group_count    = ret 
        };

        // operate
        if ((ERROR_CODE(err) = str_format(buf_ptr, buf_size, filter->format, logwatch_filter_str_format_cb, &ctx)))
            goto error;
    
    } else {
        // quote the entire line
        // XXX: overflow
        buf_ptr += str_advance(NULL, &buf_size, str_quote(buf_ptr, buf_size, line, -1));

    }

    // log
    log_info("<%s>:%s -> %s:%s", source->name, line, irc_chan_name(filter->chan->irc_chan), buf);
    
    // send it
    if ((ERROR_CODE(err) = logwatch_chan_msg(filter->chan, "[%s] %s", filter->name, buf)))
        goto error;

    // ok, stop here
    return 1;

error:
    return -ERROR_CODE(err);    
}