view src/wflogs-config.cpp @ 4:37eace15ef87

allow hourly/daily/weekly triggers for output generation, append to temp wflogs input files so daemon restart won't drop as much data
author Carl Byington <carl@five-ten-sg.com>
date Fri, 17 May 2013 12:03:21 -0700
parents 400b1de6e1c6
children a043b7d7269c
line wrap: on
line source

/*

Copyright (c) 2013 Carl Byington - 510 Software Group, released under
the GPL version 3 or any later version at your choice available at
http://www.gnu.org/licenses/gpl-3.0.txt

*/

#include "includes.h"
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <limits.h>
#include <time.h>


string_set      all_strings;// owns all the strings, only modified by the config loader thread
const int maxlen = 1000;    // used for snprintf buffers

const char *token_context;
const char *token_daily;
const char *token_file;
const char *token_hourly;
const char *token_include;
const char *token_lbrace;
const char *token_output;
const char *token_pattern;
const char *token_period;
const char *token_rbrace;
const char *token_semi;
const char *token_tempin;
const char *token_trigger;
const char *token_versions;
const char *token_weekly;
const char *token_wflogs;


////////////////////////////////////////////////
//
CONTEXT::CONTEXT(const char *nam) {
    name               = nam;
    fd                 = -1;
    len                = 0;
    fdo                = -1;
    period             = 120;
    versions           = 3;
    trigger            = NULL;
    output             = NULL;
    tempin             = NULL;
    wflogs             = NULL;
    fn                 = NULL;
    pattern            = NULL;
}


CONTEXT::~CONTEXT() {
    free_all();
}


void CONTEXT::dump() {
    printf("context %s {\n", name);
    printf("    period   %d; \n", period);
    printf("    versions %d; \n", versions);
    if (trigger) printf("    trigger  \"%s\";\n", trigger);
    printf("    output   \"%s\";\n", output);
    printf("    tempin   \"%s\";\n", tempin);
    printf("    wflogs   \"%s\";\n", wflogs);
    printf("    file     \"%s\";\n", fn);
    printf("    pattern  \"%s\";\n", pattern);
    printf("};\n\n");
}


void CONTEXT::openo(bool msg) {
    open_time = time(NULL);
    localtime_r(&open_time, &open_tm);
    fdo = ::open(tempin, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    if (fdo == -1) {
        if (msg) {
            char buf[maxlen];
            snprintf(buf, sizeof(buf), "wflogs tempin file %s not writeable", tempin);
            tokp->token_error(buf);
        }
    }
    else {
        lseek(fdo, 0, SEEK_END);
    }
}


void CONTEXT::open(bool msg) {
    fd        = ::open(fn, O_RDONLY);
    len       = 0;
    if (fd == -1) {
        if (msg) {
            char buf[maxlen];
            snprintf(buf, sizeof(buf), "syslog file %s not readable", fn);
            tokp->token_error(buf);
        }
    }
    else {
        if (debug_syslog > 1) {
            snprintf(buf, sizeof(buf), "syslog file %s opened", fn);
            my_syslog(buf);
        }
        if (msg) lseek(fd, 0, SEEK_END);
        if (fstat(fd, &openfdstat)) {
            close();
            snprintf(buf, sizeof(buf), "syslog file %s cannot stat after open", fn);
            tokp->token_error(buf);
        }
        // specify that this fd gets closed on exec, so that wflogs
        // won't have access to it.
        int oldflags = fcntl(fd, F_GETFD, 0);
        if (oldflags >= 0) {
            fcntl(fd, F_SETFD, oldflags | FD_CLOEXEC);
        }
    }
}


bool CONTEXT::write(char *p) {
    // p points to \0 at end of buf, may be destroyed
    if (failedo()) {
        openo(false);
        if (failedo()) return false;
    }
    *p = '\n';
    ::write(fdo, buf, p-buf+1);
}


bool CONTEXT::read() {
    if (failed()) {
        open(false);
        if (failed()) return false;
    }
    int n = ::read(fd, buf+len, buflen-len);
    bool have = (n > 0);
    if (have) {
        len += n;
        while (true) {
            char *p = (char*)memchr(buf, '\n', len);
            if (!p) break;
            n = p-buf;
            *p = '\0';
            process(p);     // process null terminated string, but may destroy the null
            len -= n+1;
            memmove(buf, p+1, len);
        }
        // no <lf> in a full buffer
        if (len == buflen) len = 0;
    }
    else {
        // check for file close
        struct stat filenamest;
        if (0 == stat(fn, &filenamest)) {
            if ((filenamest.st_dev != openfdstat.st_dev) ||
                (filenamest.st_ino != openfdstat.st_ino)) {
                close();
            }
        }
        else {
            // filename no longer exists
            close();
        }
    }
    check_wflog();
    return have;
}


void CONTEXT::closeo() {
    if (fdo != -1) ::close(fdo);
    fdo = -1;
}


void CONTEXT::close() {
    if (debug_syslog > 1) {
        snprintf(buf, sizeof(buf), "syslog file %s closed", fn);
        my_syslog(buf);
    }
    if (fd != -1) ::close(fd);
    fd = -1;
}


void CONTEXT::process(char *p) {
    // p points to \0 at end of buf, may be destroyed
    if (pattern) {
        if (0 == regexec(&re, buf, 0, NULL, 0)) {
            if (debug_syslog > 2) {
                my_syslog(buf); // show lines with matches
            }
            write(p);
        }
    }
}


bool CONTEXT::check_wflog_time() {
    time_t now_time = time(NULL);
    tm now_tm;
    localtime_r(&now_time, &now_tm);
    return (open_time + period < now_time) || \
           ((trigger == token_hourly) && (now_tm.tm_hour != open_tm.tm_hour)) || \
           ((trigger == token_daily)  && (now_tm.tm_wday != open_tm.tm_wday)) || \
           ((trigger == token_weekly) && (now_tm.tm_wday != open_tm.tm_wday) && (now_tm.tm_wday == 0));
}


void CONTEXT::check_wflog() {
    if ((fdo != -1) && check_wflog_time()) {
        closeo();
        // rename previous wflog html output files
        char buf[maxlen];
        char fn1[maxlen];
        char fn2[maxlen];
        for (int i=1; i<versions; i++) {
            int j = versions - 1 - i;
            int k = j + 1;
            snprintf(fn1, maxlen, output, j);
            snprintf(fn2, maxlen, output, k);
            snprintf(buf, maxlen, "mv \"%s\" \"%s\"", fn1, fn2);
            system(buf);
        }
        snprintf(buf, maxlen, wflogs, fn1);
        system(buf);
        openo(false);
    }
}


void CONTEXT::free_all() {
    regfree(&re);
    close();
    closeo();
}


////////////////////////////////////////////////
//
CONFIG::CONFIG() {
    reference_count    = 0;
    generation         = 0;
    load_time          = 0;
}


CONFIG::~CONFIG() {
    for (context_list::iterator i=contexts.begin(); i!=contexts.end(); i++) {
        CONTEXT *c = *i;
        delete c;
    }
}


void CONFIG::dump() {
    for (context_list::iterator i=contexts.begin(); i!=contexts.end(); i++) {
        CONTEXTP c = *i;
        c->dump();
    }
}


bool CONFIG::read() {
    bool rc = false;
    for (context_list::iterator i=contexts.begin(); i!=contexts.end(); i++) {
        CONTEXT *c = *i;
        rc |= c->read();
    }
}

void CONFIG::sleep(int duration, time_t &previous) {
    ::sleep(duration);
    time_t now = time(NULL);
    previous = now;
}


void CONFIG::free_all() {
    for (context_list::iterator i=contexts.begin(); i!=contexts.end(); i++) {
        CONTEXT *c = *i;
        c->free_all();
    }
}


////////////////////////////////////////////////
// helper to discard the strings held by a string_set
//
void discard(string_set &s) {
    for (string_set::iterator i=s.begin(); i!=s.end(); i++) {
        free((void*)*i);
    }
    s.clear();
}


////////////////////////////////////////////////
// helper to register a string in a string set
//
const char* register_string(string_set &s, const char *name) {
    string_set::const_iterator i = s.find(name);
    if (i != s.end()) return *i;
    char *x = strdup(name);
    s.insert(x);
    return x;
}


////////////////////////////////////////////////
// register a global string
//
const char* register_string(const char *name) {
    return register_string(all_strings, name);
}


////////////////////////////////////////////////
// clear all global strings, helper for valgrind checking
//
void clear_strings() {
	discard(all_strings);
}


////////////////////////////////////////////////
//
bool tsa(TOKEN &tok, const char *token);
bool tsa(TOKEN &tok, const char *token) {
    const char *have = tok.next();
    if (have == token) return true;
    tok.token_error(token, have);
    return false;
}


////////////////////////////////////////////////
//
bool parse_context(TOKEN &tok, CONFIG &dc, CONTEXTP parent);
bool parse_context(TOKEN &tok, CONFIG &dc, CONTEXTP parent) {
    const char *name = tok.next();
    if (!tsa(tok, token_lbrace)) return false;
    CONTEXTP con = new CONTEXT(name);
    con->set_token(tok);
    while (true) {
        const char *have = tok.next();
        if (!have) break;
        if (have == token_rbrace) break;  // done
        if (have == token_period) {
            have = tok.next();
            con->set_period(atoi(have));
            if (!tsa(tok, token_semi)) return false;
        }
        else if (have == token_versions) {
            have = tok.next();
            con->set_versions(atoi(have));
            if (!tsa(tok, token_semi)) return false;
        }
        else if (have == token_output) {
            con->set_output(tok.next());
            if (!tsa(tok, token_semi)) return false;
        }
        else if (have == token_tempin) {
            con->set_tempin(tok.next());
            if (!tsa(tok, token_semi)) return false;
        }
        else if (have == token_wflogs) {
            con->set_wflogs(tok.next());
            if (!tsa(tok, token_semi)) return false;
        }
        else if (have == token_file) {
            con->set_file(tok.next());
            if (!tsa(tok, token_semi)) return false;
        }
        else if (have == token_pattern) {
            con->pattern = tok.next();
            int rc = regcomp(&con->re, con->pattern, REG_ICASE | REG_EXTENDED);
            if (rc) {
                char bu[maxlen];
                regerror(rc, &con->re, bu, maxlen);
                char buf[maxlen];
                snprintf(buf, sizeof(buf), "pattern %s not valid - %s", con->pattern, bu);
                tok.token_error(buf);
                con->pattern = NULL;
            }
            if (!tsa(tok, token_semi)) return false;
        }
        else if (have == token_trigger) {
            have = tok.next();
            if ((have == token_hourly) || (have == token_daily) || (have == token_weekly)) {
                con->trigger = have;
            }
            else {
                tok.token_error("hourly/daily/weekly", have);
            }
            if (!tsa(tok, token_semi)) return false;
        }
        else {
            tok.token_error("period/versions/output/tempin/wflogs/file/pattern", have);
            return false;
        }
    }
    if (!tsa(tok, token_semi)) {
        delete con;
        return false;
    }
    dc.add_context(con);
    return true;
}


////////////////////////////////////////////////
// parse a config file
//
bool load_conf(CONFIG &dc, const char *fn) {
    int count = 0;
    TOKEN tok(fn, &dc.config_files);
    while (true) {
        const char *have = tok.next();
        if (!have) break;
        if (have == token_context) {
            if (!parse_context(tok, dc, NULL)) {
                tok.token_error("load_conf() failed to parse context");
                return false;
            }
            else count++;
        }
        else {
            tok.token_error(token_context, have);
            return false;
        }
    }
    tok.token_error("load_conf() found %d contexts in %s", count, fn);
    return (!dc.contexts.empty());
}


////////////////////////////////////////////////
// init the tokens
//
void token_init() {
    token_context    = register_string("context");
    token_daily      = register_string("daily");
    token_file       = register_string("file");
    token_hourly     = register_string("hourly");
    token_include    = register_string("include");
    token_lbrace     = register_string("{");
    token_output     = register_string("output");
    token_pattern    = register_string("pattern");
    token_period     = register_string("period");
    token_rbrace     = register_string("}");
    token_semi       = register_string(";");
    token_tempin     = register_string("tempin");
    token_trigger    = register_string("trigger");
    token_versions   = register_string("versions");
    token_weekly     = register_string("weekly");
    token_wflogs     = register_string("wflogs");
}