Mercurial > wflogs-daemon
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"); }